Passed
Push — master ( 1713a6...bcb549 )
by Peter
02:05
created

jsPlumb.Endpoint   F

Complexity

Conditions 12
Paths 0

Size

Total Lines 851

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
nop 1
dl 0
loc 851
rs 2
c 0
b 0
f 0
nc 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like jsPlumb.Endpoint often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/**
2
* jsBezier-0.6
3
*
4
* Copyright (c) 2010 - 2013 Simon Porritt ([email protected])
5
*
6
* licensed under the MIT license.
7
* 
8
* a set of Bezier curve functions that deal with Beziers, used by jsPlumb, and perhaps useful for other people.  These functions work with Bezier
9
* curves of arbitrary degree.
10
*
11
* - functions are all in the 'jsBezier' namespace.  
12
* 
13
* - all input points should be in the format {x:.., y:..}. all output points are in this format too.
14
* 
15
* - all input curves should be in the format [ {x:.., y:..}, {x:.., y:..}, {x:.., y:..}, {x:.., y:..} ]
16
* 
17
* - 'location' as used as an input here refers to a decimal in the range 0-1 inclusive, which indicates a point some proportion along the length
18
* of the curve.  location as output has the same format and meaning.
19
* 
20
* 
21
* Function List:
22
* --------------
23
* 
24
* distanceFromCurve(point, curve)
25
* 
26
* 	Calculates the distance that the given point lies from the given Bezier.  Note that it is computed relative to the center of the Bezier,
27
* so if you have stroked the curve with a wide pen you may wish to take that into account!  The distance returned is relative to the values 
28
* of the curve and the point - it will most likely be pixels.
29
* 
30
* gradientAtPoint(curve, location)
31
* 
32
* 	Calculates the gradient to the curve at the given location, as a decimal between 0 and 1 inclusive.
33
*
34
* gradientAtPointAlongCurveFrom (curve, location)
35
*
36
*	Calculates the gradient at the point on the given curve that is 'distance' units from location. 
37
* 
38
* nearestPointOnCurve(point, curve) 
39
* 
40
*	Calculates the nearest point to the given point on the given curve.  The return value of this is a JS object literal, containing both the
41
*point's coordinates and also the 'location' of the point (see above), for example:  { point:{x:551,y:150}, location:0.263365 }.
42
* 
43
* pointOnCurve(curve, location)
44
* 
45
* 	Calculates the coordinates of the point on the given Bezier curve at the given location.  
46
* 		
47
* pointAlongCurveFrom(curve, location, distance)
48
* 
49
* 	Calculates the coordinates of the point on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
50
* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
51
*
52
* locationAlongCurveFrom(curve, location, distance)
53
* 
54
* 	Calculates the location on the given curve that is 'distance' units from location.  'distance' should be in the same coordinate
55
* space as that used to construct the Bezier curve.  For an HTML Canvas usage, for example, distance would be a measure of pixels.
56
* 
57
* perpendicularToCurveAt(curve, location, length, distance)
58
* 
59
* 	Calculates the perpendicular to the given curve at the given location.  length is the length of the line you wish for (it will be centered
60
* on the point at 'location'). distance is optional, and allows you to specify a point along the path from the given location as the center of
61
* the perpendicular returned.  The return value of this is an array of two points: [ {x:...,y:...}, {x:...,y:...} ].  
62
*  
63
* 
64
*/
65
66
(function() {
67
	
68
	if(typeof Math.sgn == "undefined") {
69
		Math.sgn = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
70
	}
71
	
72
	var Vectors = {
73
			subtract 	: 	function(v1, v2) { return {x:v1.x - v2.x, y:v1.y - v2.y }; },
74
			dotProduct	: 	function(v1, v2) { return (v1.x * v2.x)  + (v1.y * v2.y); },
75
			square		:	function(v) { return Math.sqrt((v.x * v.x) + (v.y * v.y)); },
76
			scale		:	function(v, s) { return {x:v.x * s, y:v.y * s }; }
77
		},
78
		
79
		maxRecursion = 64, 
80
		flatnessTolerance = Math.pow(2.0,-maxRecursion-1);
81
82
	/**
83
	 * Calculates the distance that the point lies from the curve.
84
	 * 
85
	 * @param point a point in the form {x:567, y:3342}
86
	 * @param curve a Bezier curve in the form [{x:..., y:...}, {x:..., y:...}, {x:..., y:...}, {x:..., y:...}].  note that this is currently
87
	 * hardcoded to assume cubiz beziers, but would be better off supporting any degree. 
88
	 * @return a JS object literal containing location and distance, for example: {location:0.35, distance:10}.  Location is analogous to the location
89
	 * argument you pass to the pointOnPath function: it is a ratio of distance travelled along the curve.  Distance is the distance in pixels from
90
	 * the point to the curve. 
91
	 */
92
	var _distanceFromCurve = function(point, curve) {
93
		var candidates = [],     
94
	    	w = _convertToBezier(point, curve),
95
	    	degree = curve.length - 1, higherDegree = (2 * degree) - 1,
96
	    	numSolutions = _findRoots(w, higherDegree, candidates, 0),
97
			v = Vectors.subtract(point, curve[0]), dist = Vectors.square(v), t = 0.0;
98
99
	    for (var i = 0; i < numSolutions; i++) {
100
			v = Vectors.subtract(point, _bezier(curve, degree, candidates[i], null, null));
101
	    	var newDist = Vectors.square(v);
102
	    	if (newDist < dist) {
103
	            dist = newDist;
104
	        	t = candidates[i];
105
		    }
106
	    }
107
	    v = Vectors.subtract(point, curve[degree]);
108
		newDist = Vectors.square(v);
109
	    if (newDist < dist) {
110
	        dist = newDist;
111
	    	t = 1.0;
112
	    }
113
		return {location:t, distance:dist};
114
	};
115
	/**
116
	 * finds the nearest point on the curve to the given point.
117
	 */
118
	var _nearestPointOnCurve = function(point, curve) {    
119
		var td = _distanceFromCurve(point, curve);
120
	    return {point:_bezier(curve, curve.length - 1, td.location, null, null), location:td.location};
121
	};
122
	var _convertToBezier = function(point, curve) {
123
		var degree = curve.length - 1, higherDegree = (2 * degree) - 1,
124
	    	c = [], d = [], cdTable = [], w = [],
125
	    	z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ];	
126
	    	
127
	    for (var i = 0; i <= degree; i++) c[i] = Vectors.subtract(curve[i], point);
128
	    for (var i = 0; i <= degree - 1; i++) { 
129
			d[i] = Vectors.subtract(curve[i+1], curve[i]);
130
			d[i] = Vectors.scale(d[i], 3.0);
131
	    }
132
	    for (var row = 0; row <= degree - 1; row++) {
133
			for (var column = 0; column <= degree; column++) {
134
				if (!cdTable[row]) cdTable[row] = [];
135
		    	cdTable[row][column] = Vectors.dotProduct(d[row], c[column]);
136
			}
137
	    }
138
	    for (i = 0; i <= higherDegree; i++) {
139
			if (!w[i]) w[i] = [];
140
			w[i].y = 0.0;
141
			w[i].x = parseFloat(i) / higherDegree;
142
	    }
143
	    var n = degree, m = degree-1;
144
	    for (var k = 0; k <= n + m; k++) {
145
			var lb = Math.max(0, k - m),
146
				ub = Math.min(k, n);
147
			for (i = lb; i <= ub; i++) {
148
		    	j = k - i;
149
		    	w[i+j].y += cdTable[j][i] * z[j][i];
150
			}
151
	    }
152
	    return w;
153
	};
154
	/**
155
	 * counts how many roots there are.
156
	 */
157
	var _findRoots = function(w, degree, t, depth) {  
158
	    var left = [], right = [],	
159
	    	left_count, right_count,	
160
	    	left_t = [], right_t = [];
161
	    	
162
	    switch (_getCrossingCount(w, degree)) {
163
	       	case 0 : {	
164
	       		return 0;	
165
	       	}
166
	       	case 1 : {	
167
	       		if (depth >= maxRecursion) {
168
	       			t[0] = (w[0].x + w[degree].x) / 2.0;
169
	       			return 1;
170
	       		}
171
	       		if (_isFlatEnough(w, degree)) {
172
	       			t[0] = _computeXIntercept(w, degree);
173
	       			return 1;
174
	       		}
175
	       		break;
176
	       	}
177
	    }
178
	    _bezier(w, degree, 0.5, left, right);
179
	    left_count  = _findRoots(left,  degree, left_t, depth+1);
180
	    right_count = _findRoots(right, degree, right_t, depth+1);
181
	    for (var i = 0; i < left_count; i++) t[i] = left_t[i];
182
	    for (var i = 0; i < right_count; i++) t[i+left_count] = right_t[i];    
183
		return (left_count+right_count);
184
	};
185
	var _getCrossingCount = function(curve, degree) {
186
	    var n_crossings = 0, sign, old_sign;		    	
187
	    sign = old_sign = Math.sgn(curve[0].y);
188
	    for (var i = 1; i <= degree; i++) {
189
			sign = Math.sgn(curve[i].y);
190
			if (sign != old_sign) n_crossings++;
191
			old_sign = sign;
192
	    }
193
	    return n_crossings;
194
	};
195
	var _isFlatEnough = function(curve, degree) {
196
	    var  error,
197
	    	intercept_1, intercept_2, left_intercept, right_intercept,
198
	    	a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
199
	    a = curve[0].y - curve[degree].y;
200
	    b = curve[degree].x - curve[0].x;
201
	    c = curve[0].x * curve[degree].y - curve[degree].x * curve[0].y;
202
	
203
	    var max_distance_above = max_distance_below = 0.0;
204
	    
205
	    for (var i = 1; i < degree; i++) {
206
	        var value = a * curve[i].x + b * curve[i].y + c;       
207
	        if (value > max_distance_above)
208
	            max_distance_above = value;
209
	        else if (value < max_distance_below)
210
	        	max_distance_below = value;
211
	    }
212
	    
213
	    a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
214
	    c2 = c - max_distance_above;
215
	    det = a1 * b2 - a2 * b1;
216
	    dInv = 1.0/det;
217
	    intercept_1 = (b1 * c2 - b2 * c1) * dInv;
218
	    a2 = a; b2 = b; c2 = c - max_distance_below;
219
	    det = a1 * b2 - a2 * b1;
220
	    dInv = 1.0/det;
221
	    intercept_2 = (b1 * c2 - b2 * c1) * dInv;
222
	    left_intercept = Math.min(intercept_1, intercept_2);
223
	    right_intercept = Math.max(intercept_1, intercept_2);
224
	    error = right_intercept - left_intercept;
225
	    return (error < flatnessTolerance)? 1 : 0;
226
	};
227
	var _computeXIntercept = function(curve, degree) {
228
	    var XLK = 1.0, YLK = 0.0,
229
	    	XNM = curve[degree].x - curve[0].x, YNM = curve[degree].y - curve[0].y,
230
	    	XMK = curve[0].x - 0.0, YMK = curve[0].y - 0.0,
231
	    	det = XNM*YLK - YNM*XLK, detInv = 1.0/det,
232
	    	S = (XNM*YMK - YNM*XMK) * detInv; 
233
	    return 0.0 + XLK * S;
234
	};
235
	var _bezier = function(curve, degree, t, left, right) {
236
	    var temp = [[]];
237
	    for (var j =0; j <= degree; j++) temp[0][j] = curve[j];
238
	    for (var i = 1; i <= degree; i++) {	
239
			for (var j =0 ; j <= degree - i; j++) {
240
				if (!temp[i]) temp[i] = [];
241
				if (!temp[i][j]) temp[i][j] = {};
242
		    	temp[i][j].x = (1.0 - t) * temp[i-1][j].x + t * temp[i-1][j+1].x;
243
		    	temp[i][j].y = (1.0 - t) * temp[i-1][j].y + t * temp[i-1][j+1].y;
244
			}
245
	    }    
246
	    if (left != null) 
247
	    	for (j = 0; j <= degree; j++) left[j]  = temp[j][0];
248
	    if (right != null)
249
			for (j = 0; j <= degree; j++) right[j] = temp[degree-j][j];
250
	    
251
	    return (temp[degree][0]);
252
	};
253
	
254
	var _curveFunctionCache = {};
255
	var _getCurveFunctions = function(order) {
256
		var fns = _curveFunctionCache[order];
257
		if (!fns) {
258
			fns = [];			
259
			var f_term = function() { return function(t) { return Math.pow(t, order); }; },
260
				l_term = function() { return function(t) { return Math.pow((1-t), order); }; },
261
				c_term = function(c) { return function(t) { return c; }; },
262
				t_term = function() { return function(t) { return t; }; },
263
				one_minus_t_term = function() { return function(t) { return 1-t; }; },
264
				_termFunc = function(terms) {
265
					return function(t) {
266
						var p = 1;
267
						for (var i = 0; i < terms.length; i++) p = p * terms[i](t);
268
						return p;
269
					};
270
				};
271
			
272
			fns.push(new f_term());  // first is t to the power of the curve order		
273
			for (var i = 1; i < order; i++) {
274
				var terms = [new c_term(order)];
275
				for (var j = 0 ; j < (order - i); j++) terms.push(new t_term());
276
				for (var j = 0 ; j < i; j++) terms.push(new one_minus_t_term());
277
				fns.push(new _termFunc(terms));
278
			}
279
			fns.push(new l_term());  // last is (1-t) to the power of the curve order
280
		
281
			_curveFunctionCache[order] = fns;
282
		}
283
			
284
		return fns;
285
	};
286
	
287
	
288
	/**
289
	 * calculates a point on the curve, for a Bezier of arbitrary order.
290
	 * @param curve an array of control points, eg [{x:10,y:20}, {x:50,y:50}, {x:100,y:100}, {x:120,y:100}].  For a cubic bezier this should have four points.
291
	 * @param location a decimal indicating the distance along the curve the point should be located at.  this is the distance along the curve as it travels, taking the way it bends into account.  should be a number from 0 to 1, inclusive.
292
	 */
293
	var _pointOnPath = function(curve, location) {		
294
		var cc = _getCurveFunctions(curve.length - 1),
295
			_x = 0, _y = 0;
296
		for (var i = 0; i < curve.length ; i++) {
297
			_x = _x + (curve[i].x * cc[i](location));
298
			_y = _y + (curve[i].y * cc[i](location));
299
		}
300
		
301
		return {x:_x, y:_y};
302
	};
303
	
304
	var _dist = function(p1,p2) {
305
		return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
306
	};
307
308
	var _isPoint = function(curve) {
309
		return curve[0].x == curve[1].x && curve[0].y == curve[1].y;
310
	};
311
	
312
	/**
313
	 * finds the point that is 'distance' along the path from 'location'.  this method returns both the x,y location of the point and also
314
	 * its 'location' (proportion of travel along the path); the method below - _pointAlongPathFrom - calls this method and just returns the
315
	 * point.
316
	 */
317
	var _pointAlongPath = function(curve, location, distance) {
318
319
		if (_isPoint(curve)) {
320
			return {
321
				point:curve[0],
322
				location:location
323
			};
324
		}
325
326
		var prev = _pointOnPath(curve, location), 
327
			tally = 0, 
328
			curLoc = location, 
329
			direction = distance > 0 ? 1 : -1, 
330
			cur = null;
331
			
332
		while (tally < Math.abs(distance)) {
333
			curLoc += (0.005 * direction);
334
			cur = _pointOnPath(curve, curLoc);
335
			tally += _dist(cur, prev);	
336
			prev = cur;
337
		}
338
		return {point:cur, location:curLoc};        	
339
	};
340
	
341
	var _length = function(curve) {
342
		if (_isPoint(curve)) return 0;
343
344
		var prev = _pointOnPath(curve, 0),
345
			tally = 0,
346
			curLoc = 0,
347
			direction = 1,
348
			cur = null;
349
			
350
		while (curLoc < 1) {
351
			curLoc += (0.005 * direction);
352
			cur = _pointOnPath(curve, curLoc);
353
			tally += _dist(cur, prev);	
354
			prev = cur;
355
		}
356
		return tally;
357
	};
358
	
359
	/**
360
	 * finds the point that is 'distance' along the path from 'location'.  
361
	 */
362
	var _pointAlongPathFrom = function(curve, location, distance) {
363
		return _pointAlongPath(curve, location, distance).point;
364
	};
365
366
	/**
367
	 * finds the location that is 'distance' along the path from 'location'.  
368
	 */
369
	var _locationAlongPathFrom = function(curve, location, distance) {
370
		return _pointAlongPath(curve, location, distance).location;
371
	};
372
	
373
	/**
374
	 * returns the gradient of the curve at the given location, which is a decimal between 0 and 1 inclusive.
375
	 * 
376
	 * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
377
	 */
378
	var _gradientAtPoint = function(curve, location) {
379
		var p1 = _pointOnPath(curve, location),	
380
			p2 = _pointOnPath(curve.slice(0, curve.length - 1), location),
381
			dy = p2.y - p1.y, dx = p2.x - p1.x;
382
		return dy == 0 ? Infinity : Math.atan(dy / dx);		
383
	};
384
	
385
	/**
386
	returns the gradient of the curve at the point which is 'distance' from the given location.
387
	if this point is greater than location 1, the gradient at location 1 is returned.
388
	if this point is less than location 0, the gradient at location 0 is returned.
389
	*/
390
	var _gradientAtPointAlongPathFrom = function(curve, location, distance) {
391
		var p = _pointAlongPath(curve, location, distance);
392
		if (p.location > 1) p.location = 1;
393
		if (p.location < 0) p.location = 0;		
394
		return _gradientAtPoint(curve, p.location);		
395
	};
396
397
	/**
398
	 * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
399
	 * if distance is not supplied, the perpendicular for the given location is computed (ie. we set distance to zero).
400
	 */
401
	var _perpendicularToPathAt = function(curve, location, length, distance) {
402
		distance = distance == null ? 0 : distance;
403
		var p = _pointAlongPath(curve, location, distance),
404
			m = _gradientAtPoint(curve, p.location),
405
			_theta2 = Math.atan(-1 / m),
406
			y =  length / 2 * Math.sin(_theta2),
407
			x =  length / 2 * Math.cos(_theta2);
408
		return [{x:p.point.x + x, y:p.point.y + y}, {x:p.point.x - x, y:p.point.y - y}];
409
	};
410
	
411
	var jsBezier = window.jsBezier = {
412
		distanceFromCurve : _distanceFromCurve,
413
		gradientAtPoint : _gradientAtPoint,
414
		gradientAtPointAlongCurveFrom : _gradientAtPointAlongPathFrom,
415
		nearestPointOnCurve : _nearestPointOnCurve,
416
		pointOnCurve : _pointOnPath,		
417
		pointAlongCurveFrom : _pointAlongPathFrom,
418
		perpendicularToCurveAt : _perpendicularToPathAt,
419
		locationAlongCurveFrom:_locationAlongPathFrom,
420
		getLength:_length
421
	};
422
})();
423
424
/**
425
 * jsPlumbGeom v0.1
426
 *
427
 * Various geometry functions written as part of jsPlumb and perhaps useful for others.
428
 *
429
 * Copyright (c) 2013 Simon Porritt
430
 *
431
 * Permission is hereby granted, free of charge, to any person
432
 * obtaining a copy of this software and associated documentation
433
 * files (the "Software"), to deal in the Software without
434
 * restriction, including without limitation the rights to use,
435
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
436
 * copies of the Software, and to permit persons to whom the
437
 * Software is furnished to do so, subject to the following
438
 * conditions:
439
 *
440
 * The above copyright notice and this permission notice shall be
441
 * included in all copies or substantial portions of the Software.
442
 *
443
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
444
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
445
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
446
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
447
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
448
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
449
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
450
 * OTHER DEALINGS IN THE SOFTWARE.
451
 */
452
;(function() {
453
454
	
455
	"use strict";
456
457
	// Establish the root object, `window` in the browser, or `global` on the server.
458
	var root = this;
459
	var jsPlumbGeom;
460
	if (typeof exports !== 'undefined') {
461
		jsPlumbGeom = exports;
462
	} else {
463
		jsPlumbGeom = root.jsPlumbGeom = {};
464
	}
465
466
	var _isa = function(a) { return Object.prototype.toString.call(a) === "[object Array]"; },
467
		_pointHelper = function(p1, p2, fn) {
468
		    p1 = _isa(p1) ? p1 : [p1.x, p1.y];
469
		    p2 = _isa(p2) ? p2 : [p2.x, p2.y];    
470
		    return fn(p1, p2);
471
		},
472
		/**
473
		* @name jsPlumbGeom.gradient
474
		* @function
475
		* @desc Calculates the gradient of a line between the two points.
476
		* @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
477
		* @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
478
		* @return {Float} The gradient of a line between the two points.
479
		*/
480
		_gradient = jsPlumbGeom.gradient = function(p1, p2) {
481
		    return _pointHelper(p1, p2, function(_p1, _p2) { 
482
		        if (_p2[0] == _p1[0])
483
		            return _p2[1] > _p1[1] ? Infinity : -Infinity;
484
		        else if (_p2[1] == _p1[1]) 
485
		            return _p2[0] > _p1[0] ? 0 : -0;
486
		        else 
487
		            return (_p2[1] - _p1[1]) / (_p2[0] - _p1[0]); 
488
		    });		
489
		},
490
		/**
491
		* @name jsPlumbGeom.normal
492
		* @function
493
		* @desc Calculates the gradient of a normal to a line between the two points.
494
		* @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
495
		* @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
496
		* @return {Float} The gradient of a normal to a line between the two points.
497
		*/
498
		_normal = jsPlumbGeom.normal = function(p1, p2) {
499
		    return -1 / _gradient(p1, p2);
500
		},
501
		/**
502
		* @name jsPlumbGeom.lineLength
503
		* @function
504
		* @desc Calculates the length of a line between the two points.
505
		* @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
506
		* @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
507
		* @return {Float} The length of a line between the two points.
508
		*/
509
		_lineLength = jsPlumbGeom.lineLength = function(p1, p2) {
510
		    return _pointHelper(p1, p2, function(_p1, _p2) {
511
		        return Math.sqrt(Math.pow(_p2[1] - _p1[1], 2) + Math.pow(_p2[0] - _p1[0], 2));			
512
		    });
513
		},
514
		/**
515
		* @name jsPlumbGeom.quadrant
516
		* @function
517
		* @desc Calculates the quadrant in which the angle between the two points lies. 
518
		* @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
519
		* @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
520
		* @return {Integer} The quadrant - 1 for upper right, 2 for lower right, 3 for lower left, 4 for upper left.
521
		*/
522
		_quadrant = jsPlumbGeom.quadrant = function(p1, p2) {
523
		    return _pointHelper(p1, p2, function(_p1, _p2) {
524
		        if (_p2[0] > _p1[0]) {
525
		            return (_p2[1] > _p1[1]) ? 2 : 1;
526
		        }
527
		        else if (_p2[0] == _p1[0]) {
528
		            return _p2[1] > _p1[1] ? 2 : 1;    
529
		        }
530
		        else {
531
		            return (_p2[1] > _p1[1]) ? 3 : 4;
532
		        }
533
		    });
534
		},
535
		/**
536
		* @name jsPlumbGeom.theta
537
		* @function
538
		* @desc Calculates the angle between the two points. 
539
		* @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
540
		* @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
541
		* @return {Float} The angle between the two points.
542
		*/
543
		_theta = jsPlumbGeom.theta = function(p1, p2) {
544
		    return _pointHelper(p1, p2, function(_p1, _p2) {
545
		        var m = _gradient(_p1, _p2),
546
		            t = Math.atan(m),
547
		            s = _quadrant(_p1, _p2);
548
		        if ((s == 4 || s== 3)) t += Math.PI;
549
		        if (t < 0) t += (2 * Math.PI);
550
		    
551
		        return t;
552
		    });
553
		},
554
		/**
555
		* @name jsPlumbGeom.intersects
556
		* @function
557
		* @desc Calculates whether or not the two rectangles intersect.
558
		* @param {Rectangle} r1 First rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}`
559
		* @param {Rectangle} r2 Second rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}`
560
		* @return {Boolean} True if the rectangles intersect, false otherwise.
561
		*/
562
		_intersects = jsPlumbGeom.intersects = function(r1, r2) {
563
		    var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h,
564
		        a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h;
565
		
566
			return  ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
567
			        ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) ||
568
			        ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||
569
			        ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) ||	
570
			        ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
571
			        ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) ||
572
			        ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) ||
573
			        ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) );
574
		},
575
		_segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ],
576
		_inverseSegmentMultipliers = [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ],
577
		/**
578
		* @name jsPlumbGeom.pointOnLine
579
		* @function
580
		* @desc Calculates a point on the line from `fromPoint` to `toPoint` that is `distance` units along the length of the line.
581
		* @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
582
		* @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
583
		* @return {Point} Point on the line, in the form `{ x:..., y:... }`.
584
		*/
585
		_pointOnLine = jsPlumbGeom.pointOnLine = function(fromPoint, toPoint, distance) {
586
		    var m = _gradient(fromPoint, toPoint),
587
		        s = _quadrant(fromPoint, toPoint),
588
		        segmentMultiplier = distance > 0 ? _segmentMultipliers[s] : _inverseSegmentMultipliers[s],
589
		        theta = Math.atan(m),
590
		        y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1],
591
		        x =  Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0];
592
		    return { x:fromPoint.x + x, y:fromPoint.y + y };
593
		},
594
		/**
595
		* @name jsPlumbGeom.perpendicularLineTo
596
		* @function
597
		* @desc Calculates a line of length `length` that is perpendicular to the line from `fromPoint` to `toPoint` and passes through `toPoint`.
598
		* @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties.
599
		* @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties.
600
		* @return {Line} Perpendicular line, in the form `[ { x:..., y:... }, { x:..., y:... } ]`.
601
		*/        
602
		_perpendicularLineTo = jsPlumbGeom.perpendicularLineTo = function(fromPoint, toPoint, length) {
603
		    var m = _gradient(fromPoint, toPoint),
604
		        theta2 = Math.atan(-1 / m),
605
		        y =  length / 2 * Math.sin(theta2),
606
		        x =  length / 2 * Math.cos(theta2);
607
		    return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}];
608
		};	
609
}).call(this);
610
/*
611
 * jsPlumb
612
 * 
613
 * Title:jsPlumb 1.5.5
614
 * 
615
 * Provides a way to visually connect elements on an HTML page, using either SVG or VML.  
616
 * 
617
 * This file contains the util functions
618
 *
619
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
620
 * 
621
 * http://jsplumb.org
622
 * http://github.com/sporritt/jsplumb
623
 * http://code.google.com/p/jsplumb
624
 * 
625
 * Dual licensed under the MIT and GPL2 licenses.
626
 */
627
628
;(function() {
629
630
    var _isa = function(a) { return Object.prototype.toString.call(a) === "[object Array]"; },
631
        _isnum = function(n) { return Object.prototype.toString.call(n) === "[object Number]"; },
632
        _iss = function(s) { return typeof s === "string"; },
633
        _isb = function(s) { return typeof s === "boolean"; },
634
        _isnull = function(s) { return s == null; },  
635
        _iso = function(o) { return o == null ? false : Object.prototype.toString.call(o) === "[object Object]"; },
636
        _isd = function(o) { return Object.prototype.toString.call(o) === "[object Date]"; },
637
        _isf = function(o) { return Object.prototype.toString.call(o) === "[object Function]"; },
638
        _ise = function(o) {
639
            for (var i in o) { if (o.hasOwnProperty(i)) return false; }
640
            return true;
641
        },
642
        pointHelper = function(p1, p2, fn) {
643
            p1 = _isa(p1) ? p1 : [p1.x, p1.y];
644
            p2 = _isa(p2) ? p2 : [p2.x, p2.y];    
645
            return fn(p1, p2);
646
        };
647
    
648
    jsPlumbUtil = {        
649
        isArray : _isa,        
650
        isString : _iss,        
651
        isBoolean: _isb,        
652
        isNull : _isnull,        
653
        isObject : _iso,
654
        isDate : _isd,
655
        isFunction: _isf,
656
        isEmpty:_ise,
657
        isNumber:_isnum,
658
        clone : function(a) {
659
            if (_iss(a)) return "" + a;
660
            else if (_isb(a)) return !!a;
661
            else if (_isd(a)) return new Date(a.getTime());
662
            else if (_isf(a)) return a;
663
            else if (_isa(a)) {
664
                var b = [];
665
                for (var i = 0; i < a.length; i++)
666
                    b.push(this.clone(a[i]));
667
                return b;
668
            }
669
            else if (_iso(a)) {
670
                var c = {};
671
                for (var j in a)
672
                    c[j] = this.clone(a[j]);
673
                return c;		
674
            }
675
            else return a;
676
        },
677
        merge : function(a, b) {		
678
            var c = this.clone(a);		
679
            for (var i in b) {
680
                if (c[i] == null || _iss(b[i]) || _isb(b[i]))
681
                    c[i] = b[i];
682
                else {
683
                    if (_isa(b[i])/* && this.isArray(c[i])*/) {
684
                        var ar = [];
685
                        // if c's object is also an array we can keep its values.
686
                        if (_isa(c[i])) ar.push.apply(ar, c[i]);
687
                        ar.push.apply(ar, b[i]);
688
                        c[i] = ar;
689
                    }
690
                    else if(_iso(b[i])) {	
691
                        // overwite c's value with an object if it is not already one.
692
                        if (!_iso(c[i])) 
693
                            c[i] = {};
694
                        for (var j in b[i])
695
                            c[i][j] = b[i][j];
696
                    }
697
                }
698
            }
699
            return c;
700
        },
701
        copyValues:function(names, from, to) {
702
            for (var i = 0; i < names.length; i++)
703
                to[names[i]] = from[names[i]];
704
        },
705
        //
706
        // chain a list of functions, supplied by [ object, method name, args ], and return on the first
707
        // one that returns the failValue. if none return the failValue, return the successValue.
708
        //
709
        functionChain : function(successValue, failValue, fns) {        
710
            for (var i = 0; i < fns.length; i++) {
711
                var o = fns[i][0][fns[i][1]].apply(fns[i][0], fns[i][2]);
712
                if (o === failValue) {
713
                    return o;
714
                }
715
            }                
716
            return successValue;
717
        },
718
        // take the given model and expand out any parameters.
719
        populate : function(model, values) {		
720
            // for a string, see if it has parameter matches, and if so, try to make the substitutions.
721
            var getValue = function(fromString) {
722
                    var matches = fromString.match(/(\${.*?})/g);
723
                    if (matches != null) {
724
                        for (var i = 0; i < matches.length; i++) {
725
                            var val = values[matches[i].substring(2, matches[i].length - 1)];
726
                            if (val != null) {
727
                                fromString = fromString.replace(matches[i], val);
728
                            }
729
                        }							
730
                    }
731
                    return fromString;
732
                },		
733
                // process one entry.
734
                _one = function(d) {
735
                    if (d != null) {
736
                        if (_iss(d)) {
737
                            return getValue(d);
738
                        }
739
                        else if (_isa(d)) {
740
                            var r = [];	
741
                            for (var i = 0; i < d.length; i++)
742
                                r.push(_one(d[i]));
743
                            return r;
744
                        }
745
                        else if (_iso(d)) {
746
                            var s = {};
747
                            for (var j in d) {
748
                                s[j] = _one(d[j]);
749
                            }
750
                            return s;
751
                        }
752
                        else {
753
                            return d;
754
                        }
755
                    }
756
                };
757
            
758
            return _one(model);	
759
        },
760
        convertStyle : function(s, ignoreAlpha) {
761
            // TODO: jsPlumb should support a separate 'opacity' style member.
762
            if ("transparent" === s) return s;
763
            var o = s,
764
                pad = function(n) { return n.length == 1 ? "0" + n : n; },
765
                hex = function(k) { return pad(Number(k).toString(16)); },
766
                pattern = /(rgb[a]?\()(.*)(\))/;
767
            if (s.match(pattern)) {
768
                var parts = s.match(pattern)[2].split(",");
769
                o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]);
770
                if (!ignoreAlpha && parts.length == 4) 
771
                    o = o + hex(parts[3]);
772
            }
773
            return o;
774
        },
775
        findWithFunction : function(a, f) {
776
            if (a)
777
                for (var i = 0; i < a.length; i++) if (f(a[i])) return i;
778
            return -1;
779
        },
780
        clampToGrid : function(x, y, grid, dontClampX, dontClampY) {
781
            var _gridClamp = function(n, g) { 
782
                var e = n % g, 
783
                    f = Math.floor(n / g), 
784
                    inc = e >= (g / 2) ? 1 : 0; 
785
                return (f + inc) * g; 
786
            };
787
            return [
788
                dontClampX || grid == null ? x : _gridClamp(x, grid[0]),
789
                dontClampY || grid == null ? y : _gridClamp(y, grid[1])
790
            ];		
791
        },
792
        indexOf : function(l, v) {
793
            return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; });	
794
        },
795
        removeWithFunction : function(a, f) {
796
            var idx = jsPlumbUtil.findWithFunction(a, f);
797
            if (idx > -1) a.splice(idx, 1);
798
            return idx != -1;
799
        },
800
        remove : function(l, v) {
801
            var idx = jsPlumbUtil.indexOf(l, v);	
802
            if (idx > -1) l.splice(idx, 1);
803
            return idx != -1;
804
        },
805
        // TODO support insert index
806
        addWithFunction : function(list, item, hashFunction) {
807
            if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item);
808
        },
809
        addToList : function(map, key, value, insertAtStart) {
810
            var l = map[key];
811
            if (l == null) {
812
                l = [];
813
                map[key] = l;
814
            }
815
            l[insertAtStart ? "unshift" : "push"](value);
816
            return l;
817
        },
818
        //
819
        // extends the given obj (which can be an array) with the given constructor function, prototype functions, and
820
        // class members, any of which may be null.
821
        //
822
        extend : function(child, parent, _protoFn, _protoAtts) {
823
            _protoFn = _protoFn || {};
824
            _protoAtts = _protoAtts || {};
825
            parent = _isa(parent) ? parent : [ parent ];            
826
827
            for (var i = 0; i < parent.length; i++) {
828
                for (var j in parent[i].prototype) {
829
                    if(parent[i].prototype.hasOwnProperty(j)) {
830
                        child.prototype[j] = parent[i].prototype[j];
831
                    }
832
                }
833
            }
834
835
            var _makeFn = function(name) {
836
                return function() {
837
                    for (var i = 0; i < parent.length; i++) {
838
                        if (parent[i].prototype[name])
839
                            parent[i].prototype[name].apply(this, arguments);
840
                    }                    
841
                    return _protoFn[name].apply(this, arguments);
842
                };
843
            };
844
845
            for (var k in _protoFn) {
846
                child.prototype[k] = _makeFn(k);
847
            }
848
849
            return child;
850
        },
851
        uuid : function() {
852
            return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
853
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
854
                return v.toString(16);
855
            }));
856
        },
857
        logEnabled : true,
858
        log : function() {
859
            if (jsPlumbUtil.logEnabled && typeof console != "undefined") {
860
                try {
861
                    var msg = arguments[arguments.length - 1];
862
                    console.log(msg);
863
                }
864
                catch (e) {} 
865
            }
866
        },
867
        group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); },
868
        groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); },
869
        time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); },
870
        timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); },
871
        
872
        /**
873
		 * helper to remove an element from the DOM.
874
		 */
875
		removeElement : function(element) {
876
			if (element != null && element.parentNode != null) {
877
				element.parentNode.removeChild(element);
878
			}
879
		},
880
        /**
881
		 * helper to remove a list of elements from the DOM.
882
		 */
883
		removeElements : function(elements) {
884
			for ( var i = 0; i < elements.length; i++)
885
				jsPlumbUtil.removeElement(elements[i]);
886
		},
887
        /*
888
         * Function: sizeElement 
889
         * Helper to size and position an element. You would typically use
890
         * this when writing your own Connector or Endpoint implementation.
891
         * 
892
         * Parameters: 
893
         *  x - [int] x position for the element origin 
894
         *  y - [int] y position for the element origin 
895
         *  w - [int] width of the element 
896
         *  h - [int] height of the element
897
         *  
898
         */
899
        sizeElement : function(el, x, y, w, h) {
900
            if (el) {
901
                el.style.height = h + "px";
902
                el.height = h;
903
                el.style.width = w + "px";
904
                el.width = w;
905
                el.style.left = x + "px";
906
                el.style.top = y + "px";
907
            }
908
        },
909
        /**
910
        * @name jsPlumbUtil.wrap
911
        * @desc Wraps one function with another, creating a placeholder for the
912
        * wrapped function if it was null. this is used to wrap the various
913
        * drag/drop event functions - to allow jsPlumb to be notified of
914
        * important lifecycle events without imposing itself on the user's
915
        * drag/drop functionality. 
916
        * @param {Function} wrappedFunction original function to wrap; may be null.
917
        * @param {Function} newFunction function to wrap the original with.
918
        * @param {Object} [returnOnThisValue] Optional. Indicates that the wrappedFunction should 
919
        * not be executed if the newFunction returns a value matching 'returnOnThisValue'.
920
        * note that this is a simple comparison and only works for primitives right now.
921
        */        
922
        wrap : function(wrappedFunction, newFunction, returnOnThisValue) {
923
            wrappedFunction = wrappedFunction || function() { };
924
            newFunction = newFunction || function() { };
925
            return function() {
926
                var r = null;
927
                try {
928
                    r = newFunction.apply(this, arguments);
929
                } catch (e) {
930
                    jsPlumbUtil.log("jsPlumb function failed : " + e);
931
                }
932
                if (returnOnThisValue == null || (r !== returnOnThisValue)) {
933
                    try {
934
                        r = wrappedFunction.apply(this, arguments);
935
                    } catch (e) {
936
                        jsPlumbUtil.log("wrapped function failed : " + e);
937
                    }
938
                }
939
                return r;
940
            };
941
        }
942
    };
943
944
    
945
    jsPlumbUtil.EventGenerator = function() {
946
        var _listeners = {}, eventsSuspended = false;
947
        
948
        // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to
949
        // jsPlumb, but it seems feasible that people might want to manipulate this list.  the thinking is that we don't want event
950
        // listeners to bring down jsPlumb - or do we.  i can't make up my mind about this, but i know i want to hear about it if the "ready"
951
        // event fails, because then my page has most likely not initialised.  so i have this halfway-house solution.  it will be interesting
952
        // to hear what other people think.
953
        var eventsToDieOn = [ "ready" ];
954
                                        
955
        this.bind = function(event, listener, insertAtStart) {
956
            jsPlumbUtil.addToList(_listeners, event, listener, insertAtStart);     
957
            return this;        
958
        };
959
                 
960
        this.fire = function(event, value, originalEvent) {
961
            if (!eventsSuspended && _listeners[event]) {
962
                // instead of looping through the array we get a counter and a length, because it is possible
963
                // that an event fired from here could cause the object to get cleaned up, which would throw
964
                // away the listeners. so after each cycle through the loop we check to ensure we haven't
965
                // been nuked.
966
                var l = _listeners[event].length, i = 0, _gone = false, ret = null;
967
                if (!this.shouldFireEvent || this.shouldFireEvent(event, value, originalEvent)) {
968
                    while (!_gone && i < l && ret !== false) {                    
969
                    
970
                        // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this
971
                        // method will have the whole call stack available in the debugger.
972
                        if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event; }) != -1) 
973
                            _listeners[event][i](value, originalEvent);
974
                        else {
975
                            // for events we don't want to die on, catch and log.
976
                            try {                            
977
                                ret = _listeners[event][i](value, originalEvent);
978
                            } catch (e) {
979
                                jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e);
980
                            }
981
                        }
982
                        i++;
983
                        if (_listeners == null || _listeners[event] == null) _gone = true;                    
984
                    }
985
                }
986
            }
987
            return this;
988
        };
989
        
990
        this.unbind = function(event) {
991
            if (event)
992
                delete _listeners[event];
993
            else {
994
                _listeners = {};
995
            }
996
            return this;
997
        };
998
        
999
        this.getListener = function(forEvent) {
1000
            return _listeners[forEvent];
1001
        };              
1002
        this.setSuspendEvents = function(val) {
1003
            eventsSuspended = val;    
1004
        };        
1005
        this.isSuspendEvents = function() {
1006
            return eventsSuspended;
1007
        };        
1008
        this.cleanupListeners = function() {
1009
            for (var i in _listeners) {
1010
                _listeners[i].splice(0);
1011
                delete _listeners[i];
1012
            }
1013
        };
1014
    };
1015
1016
1017
    jsPlumbUtil.EventGenerator.prototype = {
1018
        cleanup:function() {
1019
            this.cleanupListeners();
1020
        }
1021
    };
1022
1023
1024
    // thanks MDC
1025
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FFunction%2Fbind
1026
    if (!Function.prototype.bind) {
1027
      Function.prototype.bind = function (oThis) {
1028
        if (typeof this !== "function") {
1029
          // closest thing possible to the ECMAScript 5 internal IsCallable function
1030
          throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
1031
        }
1032
1033
        var aArgs = Array.prototype.slice.call(arguments, 1), 
1034
            fToBind = this, 
1035
            fNOP = function () {},
1036
            fBound = function () {
1037
              return fToBind.apply(this instanceof fNOP && oThis ? this : oThis,
1038
                                   aArgs.concat(Array.prototype.slice.call(arguments)));
1039
            };
1040
1041
        fNOP.prototype = this.prototype;
1042
        fBound.prototype = new fNOP();
1043
1044
        return fBound;
1045
      };
1046
    }
1047
1048
})();
1049
/*
1050
 * jsPlumb
1051
 * 
1052
 * Title:jsPlumb 1.5.5
1053
 * 
1054
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
1055
 * elements, or VML.  
1056
 * 
1057
 * This file contains the base functionality for DOM type adapters. 
1058
 *
1059
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
1060
 * 
1061
 * http://jsplumb.org
1062
 * http://github.com/sporritt/jsplumb
1063
 * http://code.google.com/p/jsplumb
1064
 * 
1065
 * Dual licensed under the MIT and GPL2 licenses.
1066
 */
1067
;(function() {
1068
    
1069
		var canvasAvailable = !!document.createElement('canvas').getContext,
1070
		svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
1071
		// http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser
1072
		vmlAvailable = function() {		    
1073
            if (vmlAvailable.vml === undefined) { 
1074
                var a = document.body.appendChild(document.createElement('div'));
1075
            	a.innerHTML = '<v:shape id="vml_flag1" adj="1" />';
1076
            	var b = a.firstChild;
1077
            	if (b != null && b.style != null) {
1078
	            	b.style.behavior = "url(#default#VML)";
1079
	            	vmlAvailable.vml = b ? typeof b.adj == "object": true;
1080
	            }
1081
	            else
1082
	            	vmlAvailable.vml = false;
1083
            	a.parentNode.removeChild(a);
1084
            }
1085
            return vmlAvailable.vml;
1086
		};
1087
        
1088
    /**
1089
		Manages dragging for some instance of jsPlumb.
1090
	*/
1091
	var DragManager = function(_currentInstance) {		
1092
		var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {},			
1093
			// elementids mapped to the draggable to which they belong.
1094
			_draggablesForElements = {};			
1095
1096
        /**
1097
            register some element as draggable.  right now the drag init stuff is done elsewhere, and it is
1098
            possible that will continue to be the case.
1099
        */
1100
		this.register = function(el) {
1101
            var jpcl = jsPlumb.CurrentLibrary,
1102
            	_el = jpcl.getElementObject(el),
1103
            	id = _currentInstance.getId(el),                
1104
                parentOffset = jpcl.getOffset(_el);
1105
                    
1106
            if (!_draggables[id]) {
1107
                _draggables[id] = el;
1108
                _dlist.push(el);
1109
                _delements[id] = {};
1110
            }
1111
				
1112
			// look for child elements that have endpoints and register them against this draggable.
1113
			var _oneLevel = function(p, startOffset) {
1114
                if (p) {											
1115
                    for (var i = 0; i < p.childNodes.length; i++) {
1116
                        if (p.childNodes[i].nodeType != 3 && p.childNodes[i].nodeType != 8) {
1117
                            var cEl = jpcl.getElementObject(p.childNodes[i]),
1118
                                cid = _currentInstance.getId(p.childNodes[i], null, true);
1119
                            if (cid && _elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) {
1120
                                var cOff = jpcl.getOffset(cEl);
1121
                                _delements[id][cid] = {
1122
                                    id:cid,
1123
                                    offset:{
1124
                                        left:cOff.left - parentOffset.left,
1125
                                        top:cOff.top - parentOffset.top
1126
                                    }
1127
                                };
1128
                                _draggablesForElements[cid] = id;
1129
                            }
1130
                            _oneLevel(p.childNodes[i]);
1131
                        }	
1132
                    }
1133
                }
1134
			};
1135
1136
			_oneLevel(el);
1137
		};
1138
		
1139
		// refresh the offsets for child elements of this element. 
1140
		this.updateOffsets = function(elId) {
1141
			var jpcl = jsPlumb.CurrentLibrary,
1142
				el = jpcl.getElementObject(elId),
1143
				domEl = jpcl.getDOMElement(el),
1144
				id = _currentInstance.getId(domEl),
1145
				children = _delements[id],
1146
				parentOffset = jpcl.getOffset(el);
1147
				
1148
			if (children) {
1149
				for (var i in children) {
1150
					var cel = jpcl.getElementObject(i),
1151
						cOff = jpcl.getOffset(cel);
1152
						
1153
					_delements[id][i] = {
1154
						id:i,
1155
						offset:{
1156
							left:cOff.left - parentOffset.left,
1157
							top:cOff.top - parentOffset.top
1158
						}
1159
					};
1160
					_draggablesForElements[i] = id;
1161
				}
1162
			}
1163
		};
1164
1165
		/**
1166
			notification that an endpoint was added to the given el.  we go up from that el's parent
1167
			node, looking for a parent that has been registered as a draggable. if we find one, we add this
1168
			el to that parent's list of elements to update on drag (if it is not there already)
1169
		*/
1170
		this.endpointAdded = function(el) {
1171
			var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), 
1172
				c = jpcl.getElementObject(el), 
1173
				cLoc = jsPlumb.CurrentLibrary.getOffset(c),
1174
				p = el.parentNode, done = p == b;
1175
1176
			_elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1;
1177
1178
			while (p != null && p != b) {
1179
				var pid = _currentInstance.getId(p, null, true);
1180
				if (pid && _draggables[pid]) {
1181
					var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jpcl.getOffset(pEl);
1182
					
1183
					if (_delements[pid][id] == null) {						
1184
						_delements[pid][id] = {
1185
							id:id,
1186
							offset:{
1187
								left:cLoc.left - pLoc.left,
1188
								top:cLoc.top - pLoc.top
1189
							}
1190
						};
1191
						_draggablesForElements[id] = pid;
1192
					}
1193
					break;
1194
				}
1195
				p = p.parentNode;
1196
			}	
1197
		};
1198
1199
		this.endpointDeleted = function(endpoint) {
1200
			if (_elementsWithEndpoints[endpoint.elementId]) {
1201
				_elementsWithEndpoints[endpoint.elementId]--;
1202
				if (_elementsWithEndpoints[endpoint.elementId] <= 0) {
1203
					for (var i in _delements) {
1204
						if (_delements[i]) {
1205
                            delete _delements[i][endpoint.elementId];
1206
                            delete _draggablesForElements[endpoint.elementId];
1207
                        }
1208
					}
1209
				}
1210
			}		
1211
		};	
1212
		
1213
		this.changeId = function(oldId, newId) {				
1214
			_delements[newId] = _delements[oldId];			
1215
			_delements[oldId] = {};
1216
			_draggablesForElements[newId] = _draggablesForElements[oldId];
1217
			_draggablesForElements[oldId] = null;			
1218
		};
1219
1220
		this.getElementsForDraggable = function(id) {
1221
			return _delements[id];	
1222
		};
1223
1224
		this.elementRemoved = function(elementId) {
1225
			var elId = _draggablesForElements[elementId];
1226
			if (elId) {
1227
				delete _delements[elId][elementId];
1228
				delete _draggablesForElements[elementId];
1229
			}
1230
		};
1231
1232
		this.reset = function() {
1233
			_draggables = {};
1234
			_dlist = [];
1235
			_delements = {};
1236
			_elementsWithEndpoints = {};
1237
		};
1238
1239
		//
1240
		// notification drag ended. from 1.5.5 we check automatically if need to update some
1241
		// ancestor's offsets.
1242
		//
1243
		this.dragEnded = function(el) {			
1244
			var id = _currentInstance.getId(el),
1245
				ancestor = _draggablesForElements[id];
1246
1247
			if (ancestor) this.updateOffsets(ancestor);
1248
		};
1249
1250
		this.setParent = function(el, elId, p, pId) {
1251
			var current = _draggablesForElements[elId];
1252
			if (current) {
1253
				if (!_delements[pId])
1254
					_delements[pId] = {};
1255
				_delements[pId][elId] = _delements[current][elId];
1256
				delete _delements[current][elId];
1257
				var pLoc = jsPlumb.CurrentLibrary.getOffset(p),
1258
					cLoc = jsPlumb.CurrentLibrary.getOffset(el);
1259
				_delements[pId][elId].offset = {
1260
					left:cLoc.left - pLoc.left,
1261
					top:cLoc.top - pLoc.top
1262
				};				
1263
				_draggablesForElements[elId] = pId;
1264
			}			
1265
		};
1266
		
1267
	};
1268
        
1269
    // for those browsers that dont have it.  they still don't have it! but at least they won't crash.
1270
	if (!window.console)
1271
		window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} };
1272
            
1273
    window.jsPlumbAdapter = {
1274
        
1275
        headless:false,
1276
1277
        getAttribute:function(el, attName) {
1278
        	return el.getAttribute(attName);
1279
        },
1280
1281
        setAttribute:function(el, a, v) {
1282
        	el.setAttribute(a, v);
1283
        },
1284
        
1285
        appendToRoot : function(node) {
1286
            document.body.appendChild(node);
1287
        },
1288
        getRenderModes : function() {
1289
            return [ "canvas", "svg", "vml" ];
1290
        },
1291
        isRenderModeAvailable : function(m) {
1292
            return {
1293
                "canvas":canvasAvailable,
1294
                "svg":svgAvailable,
1295
                "vml":vmlAvailable()
1296
            }[m];
1297
        },
1298
        getDragManager : function(_jsPlumb) {
1299
            return new DragManager(_jsPlumb);
1300
        },
1301
        setRenderMode : function(mode) {
1302
            var renderMode;
1303
            
1304
            if (mode) {
1305
				mode = mode.toLowerCase();            
1306
			            
1307
                var canvasAvailable = this.isRenderModeAvailable("canvas"),
1308
                    svgAvailable = this.isRenderModeAvailable("svg"),
1309
                    vmlAvailable = this.isRenderModeAvailable("vml");
1310
                
1311
                // now test we actually have the capability to do this.						
1312
                if (mode === "svg") {
1313
                    if (svgAvailable) renderMode = "svg";
1314
                    else if (canvasAvailable) renderMode = "canvas";
1315
                    else if (vmlAvailable) renderMode = "vml";
1316
                }
1317
                else if (mode === "canvas" && canvasAvailable) renderMode = "canvas";
1318
                else if (vmlAvailable) renderMode = "vml";
1319
            }
1320
1321
			return renderMode;
1322
        }
1323
    };
1324
    
1325
1326
    /*
1327
1328
    addClass:
1329
1330
    add: function( elem, classNames ) {
1331
    jQuery.each((classNames || "").split(/\s+/), function(i, className){
1332
        if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
1333
            elem.className += (elem.className ? " " : "") + className;
1334
        });
1335
    },
1336
    */
1337
1338
    /*
1339
1340
	removeClass:
1341
1342
    elem.className = classNames !== undefined ?
1343
    	jQuery.grep(elem.className.split(/\s+/), function(className){
1344
    		return !jQuery.className.has( classNames, className );
1345
    	}).join(" ") :
1346
1347
*/
1348
1349
})();
1350
/**
1351
 * @module jsPlumb
1352
 * @description Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
1353
 * elements, or VML.   
1354
 * 
1355
 * - [Demo Site](http://jsplumb.org)
1356
 * - [GitHub](http://github.com/sporritt/jsplumb)
1357
 * 
1358
 * Dual licensed under the MIT and GPL2 licenses.
1359
 *
1360
 * Copyright (c) 2010 - 2013 Simon Porritt ([email protected])
1361
 */
1362
;(function() {
1363
			
1364
    var _ju = jsPlumbUtil,
1365
    	_addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_gel(el), clazz); },
1366
		_hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_gel(el), clazz); },
1367
		_removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_gel(el), clazz); },
1368
		_gel = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
1369
		_dom = function(el) { return jsPlumb.CurrentLibrary.getDOMElement(el); },		
1370
		_getOffset = function(el, _instance) {
1371
            var o = jsPlumb.CurrentLibrary.getOffset(_gel(el));
1372
			if (_instance != null) {
1373
                var z = _instance.getZoom();
1374
                return {left:o.left / z, top:o.top / z };    
1375
            }
1376
            else
1377
                return o;
1378
        },		
1379
		_getSize = function(el) {
1380
            return jsPlumb.CurrentLibrary.getSize(_gel(el));
1381
        },
1382
		
1383
		/**
1384
		 * creates a timestamp, using milliseconds since 1970, but as a string.
1385
		 */
1386
		_timestamp = function() { return "" + (new Date()).getTime(); },
1387
1388
		// helper method to update the hover style whenever it, or paintStyle, changes.
1389
		// we use paintStyle as the foundation and merge hoverPaintStyle over the
1390
		// top.
1391
		_updateHoverStyle = function(component) {
1392
			if (component._jsPlumb.paintStyle && component._jsPlumb.hoverPaintStyle) {
1393
				var mergedHoverStyle = {};
1394
				jsPlumb.extend(mergedHoverStyle, component._jsPlumb.paintStyle);
1395
				jsPlumb.extend(mergedHoverStyle, component._jsPlumb.hoverPaintStyle);
1396
				delete component._jsPlumb.hoverPaintStyle;
1397
				// we want the fillStyle of paintStyle to override a gradient, if possible.
1398
				if (mergedHoverStyle.gradient && component._jsPlumb.paintStyle.fillStyle)
1399
					delete mergedHoverStyle.gradient;
1400
				component._jsPlumb.hoverPaintStyle = mergedHoverStyle;
1401
			}
1402
		},		
1403
		events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ],
1404
		eventFilters = { "mouseout":"mouseexit" },
1405
		_updateAttachedElements = function(component, state, timestamp, sourceElement) {
1406
			var affectedElements = component.getAttachedElements();
1407
			if (affectedElements) {
1408
				for (var i = 0, j = affectedElements.length; i < j; i++) {
1409
					if (!sourceElement || sourceElement != affectedElements[i])
1410
						affectedElements[i].setHover(state, true, timestamp);			// tell the attached elements not to inform their own attached elements.
1411
				}
1412
			}
1413
		},
1414
		_splitType = function(t) { return t == null ? null : t.split(" "); },		
1415
		_applyTypes = function(component, params, doNotRepaint) {
1416
			if (component.getDefaultType) {
1417
				var td = component.getTypeDescriptor();
1418
					
1419
				var o = _ju.merge({}, component.getDefaultType());
1420
				for (var i = 0, j = component._jsPlumb.types.length; i < j; i++)
1421
					o = _ju.merge(o, component._jsPlumb.instance.getType(component._jsPlumb.types[i], td));						
1422
					
1423
				if (params) {
1424
					o = _ju.populate(o, params);
1425
				}
1426
			
1427
				component.applyType(o, doNotRepaint);					
1428
				if (!doNotRepaint) component.repaint();
1429
			}
1430
		},		
1431
1432
// ------------------------------ BEGIN jsPlumbUIComponent --------------------------------------------
1433
1434
		jsPlumbUIComponent = window.jsPlumbUIComponent = function(params) {
1435
1436
			jsPlumbUtil.EventGenerator.apply(this, arguments);
1437
1438
			var self = this, 
1439
				a = arguments, 				 				
1440
				idPrefix = self.idPrefix,
1441
				id = idPrefix + (new Date()).getTime(),
1442
				jpcl = jsPlumb.CurrentLibrary;
1443
1444
			this._jsPlumb = { 
1445
				instance: params._jsPlumb,
1446
				parameters:params.parameters || {},
1447
				paintStyle:null,
1448
				hoverPaintStyle:null,
1449
				paintStyleInUse:null,
1450
				hover:false,
1451
				beforeDetach:params.beforeDetach,
1452
				beforeDrop:params.beforeDrop,
1453
				overlayPlacements : [],
1454
				hoverClass: params.hoverClass || params._jsPlumb.Defaults.HoverClass || jsPlumb.Defaults.HoverClass,
1455
				types:[]
1456
			};
1457
1458
			this.getId = function() { return id; };	
1459
			
1460
			// all components can generate events
1461
			
1462
			if (params.events) {
1463
				for (var i in params.events)
1464
					self.bind(i, params.events[i]);
1465
			}
1466
1467
			// all components get this clone function.
1468
			// TODO issue 116 showed a problem with this - it seems 'a' that is in
1469
			// the clone function's scope is shared by all invocations of it, the classic
1470
			// JS closure problem.  for now, jsPlumb does a version of this inline where 
1471
			// it used to call clone.  but it would be nice to find some time to look
1472
			// further at this.
1473
			this.clone = function() {
1474
				var o = {};//new Object();
1475
				this.constructor.apply(o, a);
1476
				return o;
1477
			}.bind(this);				
1478
						
1479
			// user can supply a beforeDetach callback, which will be executed before a detach
1480
			// is performed; returning false prevents the detach.			
1481
			this.isDetachAllowed = function(connection) {
1482
				var r = true;
1483
				if (this._jsPlumb.beforeDetach) {
1484
					try { 
1485
						r = this._jsPlumb.beforeDetach(connection); 
1486
					}
1487
					catch (e) { _ju.log("jsPlumb: beforeDetach callback failed", e); }
1488
				}
1489
				return r;
1490
			};
1491
			
1492
			// user can supply a beforeDrop callback, which will be executed before a dropped
1493
			// connection is confirmed. user can return false to reject connection.			
1494
			this.isDropAllowed = function(sourceId, targetId, scope, connection, dropEndpoint) {
1495
				var r = this._jsPlumb.instance.checkCondition("beforeDrop", { 
1496
					sourceId:sourceId, 
1497
					targetId:targetId, 
1498
					scope:scope,
1499
					connection:connection,
1500
					dropEndpoint:dropEndpoint 
1501
				});
1502
				if (this._jsPlumb.beforeDrop) {
1503
					try { 
1504
						r = this._jsPlumb.beforeDrop({ 
1505
							sourceId:sourceId, 
1506
							targetId:targetId, 
1507
							scope:scope, 
1508
							connection:connection,
1509
							dropEndpoint:dropEndpoint
1510
						}); 
1511
					}
1512
					catch (e) { _ju.log("jsPlumb: beforeDrop callback failed", e); }
1513
				}
1514
				return r;
1515
			};													
1516
1517
		    var boundListeners = [],
1518
		    	bindAListener = function(obj, type, fn) {
1519
			    	boundListeners.push([obj, type, fn]);
1520
			    	obj.bind(type, fn);
1521
			    },
1522
		    	domListeners = [],
1523
            	bindOne = function(o, c, evt) {
1524
					var filteredEvent = eventFilters[evt] || evt,
1525
						fn = function(ee) {
1526
							c.fire(filteredEvent, c, ee);
1527
						};
1528
					domListeners.push([o, evt, fn]);
1529
					jpcl.bind(o, evt, fn);
1530
				},
1531
				unbindOne = function(o, evt, fn) {
1532
					var filteredEvent = eventFilters[evt] || evt;
1533
					jpcl.unbind(o, evt, fn);
1534
				};
1535
1536
            this.bindListeners = function(obj, _self, _hoverFunction) {
1537
                bindAListener(obj, "click", function(ep, e) { _self.fire("click", _self, e); });             
1538
             	bindAListener(obj, "dblclick", function(ep, e) { _self.fire("dblclick", _self, e); });
1539
                bindAListener(obj, "contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); });
1540
                bindAListener(obj, "mouseenter", function(ep, e) {
1541
                    if (!_self.isHover()) {
1542
                        _hoverFunction(true);
1543
                        _self.fire("mouseenter", _self, e);
1544
                    }
1545
                });
1546
                bindAListener(obj, "mouseexit", function(ep, e) {
1547
                    if (_self.isHover()) {
1548
                        _hoverFunction(false);
1549
                        _self.fire("mouseexit", _self, e);
1550
                    }
1551
                });	  
1552
                bindAListener(obj, "mousedown", function(ep, e) { _self.fire("mousedown", _self, e); });
1553
                bindAListener(obj, "mouseup", function(ep, e) { _self.fire("mouseup", _self, e); });
1554
            };
1555
1556
            this.unbindListeners = function() {
1557
            	for (var i = 0; i < boundListeners.length; i++) {
1558
            		var o = boundListeners[i];
1559
            		o[0].unbind(o[1], o[2]);
1560
            	}            	
1561
            	boundListeners = null;
1562
            };            
1563
		    
1564
		    this.attachListeners = function(o, c) {
1565
				for (var i = 0, j = events.length; i < j; i++) {
1566
					bindOne(o, c, events[i]); 			
1567
				}
1568
			};	
1569
			this.detachListeners = function() {
1570
				for (var i = 0; i < domListeners.length; i++) {
1571
					unbindOne(domListeners[i][0], domListeners[i][1], domListeners[i][2]);
1572
				}
1573
				domListeners = null;
1574
			};	   		    
1575
		    
1576
		    this.reattachListenersForElement = function(o) {
1577
			    if (arguments.length > 1) {
1578
		    		for (var i = 0, j = events.length; i < j; i++)
1579
		    			unbindOne(o, events[i]);
1580
			    	for (i = 1, j = arguments.length; i < j; i++)
1581
		    			this.attachListeners(o, arguments[i]);
1582
		    	}
1583
		    };		    	    			                      
1584
		};
1585
1586
		jsPlumbUtil.extend(jsPlumbUIComponent, jsPlumbUtil.EventGenerator, {
1587
			
1588
			getParameter : function(name) { 
1589
				return this._jsPlumb.parameters[name]; 
1590
			},
1591
			
1592
			setParameter : function(name, value) { 
1593
				this._jsPlumb.parameters[name] = value; 
1594
			},
1595
			
1596
			getParameters : function() { 
1597
				return this._jsPlumb.parameters; 
1598
			},			
1599
			
1600
			setParameters : function(p) { 
1601
				this._jsPlumb.parameters = p; 
1602
			},			
1603
			
1604
			addClass : function(clazz) {
1605
			    if (this.canvas != null)
1606
			        _addClass(this.canvas, clazz);
1607
			},
1608
						
1609
			removeClass : function(clazz) {
1610
			    if (this.canvas != null)
1611
			        _removeClass(this.canvas, clazz);
1612
			},
1613
			
1614
			setType : function(typeId, params, doNotRepaint) {				
1615
				this._jsPlumb.types = _splitType(typeId) || [];
1616
				_applyTypes(this, params, doNotRepaint);									
1617
			},
1618
			
1619
			getType : function() {
1620
				return this._jsPlumb.types;
1621
			},
1622
			
1623
			reapplyTypes : function(params, doNotRepaint) {
1624
				_applyTypes(this, params, doNotRepaint);
1625
			},
1626
			
1627
			hasType : function(typeId) {
1628
				return jsPlumbUtil.indexOf(this._jsPlumb.types, typeId) != -1;
1629
			},
1630
			
1631
			addType : function(typeId, params, doNotRepaint) {
1632
				var t = _splitType(typeId), _cont = false;
1633
				if (t != null) {
1634
					for (var i = 0, j = t.length; i < j; i++) {
1635
						if (!this.hasType(t[i])) {
1636
							this._jsPlumb.types.push(t[i]);
1637
							_cont = true;						
1638
						}
1639
					}
1640
					if (_cont) _applyTypes(this, params, doNotRepaint);
1641
				}
1642
			},
1643
			
1644
			removeType : function(typeId, doNotRepaint) {
1645
				var t = _splitType(typeId), _cont = false, _one = function(tt) {
1646
						var idx = _ju.indexOf(this._jsPlumb.types, tt);
1647
						if (idx != -1) {
1648
							this._jsPlumb.types.splice(idx, 1);
1649
							return true;
1650
						}
1651
						return false;
1652
					}.bind(this);
1653
				
1654
				if (t != null) {
1655
					for (var i = 0,j = t.length; i < j; i++) {
1656
						_cont = _one(t[i]) || _cont;
1657
					}
1658
					if (_cont) _applyTypes(this, null, doNotRepaint);
1659
				}
1660
			},
1661
			
1662
			toggleType : function(typeId, params, doNotRepaint) {
1663
				var t = _splitType(typeId);
1664
				if (t != null) {
1665
					for (var i = 0, j = t.length; i < j; i++) {
1666
						var idx = jsPlumbUtil.indexOf(this._jsPlumb.types, t[i]);
1667
						if (idx != -1)
1668
							this._jsPlumb.types.splice(idx, 1);
1669
						else
1670
							this._jsPlumb.types.push(t[i]);
1671
					}
1672
						
1673
					_applyTypes(this, params, doNotRepaint);
1674
				}
1675
			},
1676
			applyType : function(t, doNotRepaint) {
1677
				this.setPaintStyle(t.paintStyle, doNotRepaint);				
1678
				this.setHoverPaintStyle(t.hoverPaintStyle, doNotRepaint);
1679
				if (t.parameters){
1680
					for (var i in t.parameters)
1681
						this.setParameter(i, t.parameters[i]);
1682
				}
1683
			},
1684
			setPaintStyle : function(style, doNotRepaint) {
1685
//		    	this._jsPlumb.paintStyle = jsPlumb.extend({}, style);
1686
// TODO figure out if we want components to clone paintStyle so as not to share it.
1687
				this._jsPlumb.paintStyle = style;
1688
		    	this._jsPlumb.paintStyleInUse = this._jsPlumb.paintStyle;
1689
		    	_updateHoverStyle(this);
1690
		    	if (!doNotRepaint) this.repaint();
1691
		    },
1692
		    getPaintStyle : function() {
1693
		    	return this._jsPlumb.paintStyle;
1694
		    },
1695
		    setHoverPaintStyle : function(style, doNotRepaint) {		    	
1696
		    	//this._jsPlumb.hoverPaintStyle = jsPlumb.extend({}, style);
1697
// TODO figure out if we want components to clone paintStyle so as not to share it.		    	
1698
		    	this._jsPlumb.hoverPaintStyle = style;
1699
		    	_updateHoverStyle(this);
1700
		    	if (!doNotRepaint) this.repaint();
1701
		    },
1702
		    getHoverPaintStyle : function() {
1703
		    	return this._jsPlumb.hoverPaintStyle;
1704
		    },
1705
			cleanup:function() {		
1706
				this.unbindListeners();
1707
				this.detachListeners();
1708
			},
1709
			destroy:function() {
1710
				this.cleanupListeners();
1711
				this.clone = null;				
1712
				this._jsPlumb = null;
1713
			},
1714
			
1715
			isHover : function() { return this._jsPlumb.hover; },
1716
			
1717
			setHover : function(hover, ignoreAttachedElements, timestamp) {
1718
				var jpcl = jsPlumb.CurrentLibrary;
1719
		    	// while dragging, we ignore these events.  this keeps the UI from flashing and
1720
		    	// swishing and whatevering.
1721
				if (this._jsPlumb && !this._jsPlumb.instance.currentlyDragging && !this._jsPlumb.instance.isHoverSuspended()) {
1722
		    
1723
			    	this._jsPlumb.hover = hover;
1724
                        
1725
                    if (this.canvas != null) {
1726
                        if (this._jsPlumb.instance.hoverClass != null) {                            
1727
                            jpcl[hover ? "addClass" : "removeClass"](this.canvas, this._jsPlumb.instance.hoverClass);						                            
1728
                        }                                              
1729
                    }
1730
		   		 	if (this._jsPlumb.hoverPaintStyle != null) {
1731
						this._jsPlumb.paintStyleInUse = hover ? this._jsPlumb.hoverPaintStyle : this._jsPlumb.paintStyle;
1732
						if (!this._jsPlumb.instance.isSuspendDrawing()) {
1733
							timestamp = timestamp || _timestamp();
1734
							this.repaint({timestamp:timestamp, recalc:false});
1735
						}
1736
					}
1737
					// get the list of other affected elements, if supported by this component.
1738
					// for a connection, its the endpoints.  for an endpoint, its the connections! surprise.
1739
					if (this.getAttachedElements && !ignoreAttachedElements)
1740
						_updateAttachedElements(this, hover, _timestamp(), this);
1741
				}
1742
		    }
1743
		});
1744
1745
// ------------------------------ END jsPlumbUIComponent --------------------------------------------
1746
1747
// ------------------------------ BEGIN OverlayCapablejsPlumbUIComponent --------------------------------------------
1748
1749
		var _internalLabelOverlayId = "__label",
1750
			// helper to get the index of some overlay
1751
			_getOverlayIndex = function(component, id) {
1752
				var idx = -1;
1753
				for (var i = 0, j = component._jsPlumb.overlays.length; i < j; i++) {
1754
					if (id === component._jsPlumb.overlays[i].id) {
1755
						idx = i;
1756
						break;
1757
					}
1758
				}
1759
				return idx;
1760
			},
1761
			// this is a shortcut helper method to let people add a label as
1762
			// overlay.						
1763
			_makeLabelOverlay = function(component, params) {
1764
1765
				var _params = {
1766
					cssClass:params.cssClass,
1767
					labelStyle : component.labelStyle,					
1768
					id:_internalLabelOverlayId,
1769
					component:component,
1770
					_jsPlumb:component._jsPlumb.instance  // TODO not necessary, since the instance can be accessed through the component.
1771
				},
1772
				mergedParams = jsPlumb.extend(_params, params);
1773
1774
				return new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()].Label( mergedParams );
1775
			},
1776
			_processOverlay = function(component, o) {
1777
				var _newOverlay = null;
1778
				if (_ju.isArray(o)) {	// this is for the shorthand ["Arrow", { width:50 }] syntax
1779
					// there's also a three arg version:
1780
					// ["Arrow", { width:50 }, {location:0.7}] 
1781
					// which merges the 3rd arg into the 2nd.
1782
					var type = o[0],
1783
						// make a copy of the object so as not to mess up anyone else's reference...
1784
						p = jsPlumb.extend({component:component, _jsPlumb:component._jsPlumb.instance}, o[1]);
1785
					if (o.length == 3) jsPlumb.extend(p, o[2]);
1786
					_newOverlay = new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()][type](p);					
1787
				} else if (o.constructor == String) {
1788
					_newOverlay = new jsPlumb.Overlays[component._jsPlumb.instance.getRenderMode()][o]({component:component, _jsPlumb:component._jsPlumb.instance});
1789
				} else {
1790
					_newOverlay = o;
1791
				}										
1792
					
1793
				component._jsPlumb.overlays.push(_newOverlay);
1794
			},
1795
			_calculateOverlaysToAdd = function(component, params) {
1796
				var defaultKeys = component.defaultOverlayKeys || [], o = params.overlays,
1797
					checkKey = function(k) {
1798
						return component._jsPlumb.instance.Defaults[k] || jsPlumb.Defaults[k] || [];
1799
					};
1800
				
1801
				if (!o) o = [];
1802
1803
				for (var i = 0, j = defaultKeys.length; i < j; i++)
1804
					o.unshift.apply(o, checkKey(defaultKeys[i]));
1805
				
1806
				return o;
1807
			},		
1808
			OverlayCapableJsPlumbUIComponent = window.OverlayCapableJsPlumbUIComponent = function(params) {
1809
1810
				jsPlumbUIComponent.apply(this, arguments);
1811
				this._jsPlumb.overlays = [];			
1812
1813
				var _overlays = _calculateOverlaysToAdd(this, params);
1814
				if (_overlays) {
1815
					for (var i = 0, j = _overlays.length; i < j; i++) {
1816
						_processOverlay(this, _overlays[i]);
1817
					}
1818
				}
1819
				
1820
				if (params.label) {
1821
					var loc = params.labelLocation || this.defaultLabelLocation || 0.5,
1822
						labelStyle = params.labelStyle || this._jsPlumb.instance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;
1823
1824
					this._jsPlumb.overlays.push(_makeLabelOverlay(this, {
1825
						label:params.label,
1826
						location:loc,
1827
						labelStyle:labelStyle
1828
					}));
1829
				}			                                  
1830
			};
1831
1832
		jsPlumbUtil.extend(OverlayCapableJsPlumbUIComponent, jsPlumbUIComponent, {
1833
			applyType : function(t, doNotRepaint) {			
1834
				this.removeAllOverlays(doNotRepaint);
1835
				if (t.overlays) {
1836
					for (var i = 0, j = t.overlays.length; i < j; i++)
1837
						this.addOverlay(t.overlays[i], true);
1838
				}
1839
			},
1840
			setHover : function(hover, ignoreAttachedElements, timestamp) {            
1841
				if (this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) {
1842
	                for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++) {
1843
						this._jsPlumb.overlays[i][hover ? "addClass":"removeClass"](this._jsPlumb.instance.hoverClass);
1844
					}
1845
				}
1846
            },
1847
            addOverlay : function(overlay, doNotRepaint) { 
1848
				_processOverlay(this, overlay); 
1849
				if (!doNotRepaint) this.repaint();
1850
			},
1851
			getOverlay : function(id) {
1852
				var idx = _getOverlayIndex(this, id);
1853
				return idx >= 0 ? this._jsPlumb.overlays[idx] : null;
1854
			},			
1855
			getOverlays : function() {
1856
				return this._jsPlumb.overlays;
1857
			},			
1858
			hideOverlay : function(id) {
1859
				var o = this.getOverlay(id);
1860
				if (o) o.hide();
1861
			},
1862
			hideOverlays : function() {
1863
				for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++)
1864
					this._jsPlumb.overlays[i].hide();
1865
			},
1866
			showOverlay : function(id) {
1867
				var o = this.getOverlay(id);
1868
				if (o) o.show();
1869
			},
1870
			showOverlays : function() {
1871
				for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++)
1872
					this._jsPlumb.overlays[i].show();
1873
			},
1874
			removeAllOverlays : function(doNotRepaint) {
1875
				for (var i = 0, j = this._jsPlumb.overlays.length; i < j; i++) {
1876
					if (this._jsPlumb.overlays[i].cleanup) this._jsPlumb.overlays[i].cleanup();
1877
				}
1878
1879
				this._jsPlumb.overlays.splice(0, this._jsPlumb.overlays.length);
1880
				this._jsPlumb.overlayPositions = null;
1881
				if (!doNotRepaint)
1882
					this.repaint();
1883
			},
1884
			removeOverlay : function(overlayId) {
1885
				var idx = _getOverlayIndex(this, overlayId);
1886
				if (idx != -1) {
1887
					var o = this._jsPlumb.overlays[idx];
1888
					if (o.cleanup) o.cleanup();
1889
					this._jsPlumb.overlays.splice(idx, 1);
1890
					this._jsPlumb.overlayPositions && delete this._jsPlumb.overlayPositions[overlayId];
1891
				}
1892
			},
1893
			removeOverlays : function() {
1894
				for (var i = 0, j = arguments.length; i < j; i++)
1895
					this.removeOverlay(arguments[i]);
1896
			},
1897
			getLabel : function() {
1898
				var lo = this.getOverlay(_internalLabelOverlayId);
1899
				return lo != null ? lo.getLabel() : null;
1900
			},		
1901
			getLabelOverlay : function() {
1902
				return this.getOverlay(_internalLabelOverlayId);
1903
			},
1904
			setLabel : function(l) {
1905
				var lo = this.getOverlay(_internalLabelOverlayId);
1906
				if (!lo) {
1907
					var params = l.constructor == String || l.constructor == Function ? { label:l } : l;
1908
					lo = _makeLabelOverlay(this, params);	
1909
					this._jsPlumb.overlays.push(lo);
1910
				}
1911
				else {
1912
					if (l.constructor == String || l.constructor == Function) lo.setLabel(l);
1913
					else {
1914
						if (l.label) lo.setLabel(l.label);
1915
						if (l.location) lo.setLocation(l.location);
1916
					}
1917
				}
1918
				
1919
				if (!this._jsPlumb.instance.isSuspendDrawing()) 
1920
					this.repaint();
1921
			},
1922
			cleanup:function() {
1923
				for (var i = 0; i < this._jsPlumb.overlays.length; i++) {
1924
					this._jsPlumb.overlays[i].cleanup();
1925
					this._jsPlumb.overlays[i].destroy();
1926
				}
1927
				this._jsPlumb.overlays.splice(0);
1928
				this._jsPlumb.overlayPositions = null;
1929
			},
1930
			setVisible:function(v) {
1931
				this[v ? "showOverlays" : "hideOverlays"]();
1932
			},
1933
			setAbsoluteOverlayPosition:function(overlay, xy) {
1934
				this._jsPlumb.overlayPositions = this._jsPlumb.overlayPositions || {};
1935
				this._jsPlumb.overlayPositions[overlay.id] = xy;
1936
			},
1937
			getAbsoluteOverlayPosition:function(overlay) {
1938
				return this._jsPlumb.overlayPositions ? this._jsPlumb.overlayPositions[overlay.id] : null;
1939
			}
1940
		});		
1941
1942
// ------------------------------ END OverlayCapablejsPlumbUIComponent --------------------------------------------
1943
		
1944
		var _jsPlumbInstanceIndex = 0,
1945
			getInstanceIndex = function() {
1946
				var i = _jsPlumbInstanceIndex + 1;
1947
				_jsPlumbInstanceIndex++;
1948
				return i;
1949
			};
1950
1951
		var jsPlumbInstance = window.jsPlumbInstance = function(_defaults) {
1952
				
1953
			this.Defaults = {
1954
				Anchor : "BottomCenter",
1955
				Anchors : [ null, null ],
1956
	            ConnectionsDetachable : true,
1957
	            ConnectionOverlays : [ ],
1958
	            Connector : "Bezier",
1959
				Container : null,
1960
				DoNotThrowErrors:false,
1961
				DragOptions : { },
1962
				DropOptions : { },
1963
				Endpoint : "Dot",
1964
				EndpointOverlays : [ ],
1965
				Endpoints : [ null, null ],
1966
				EndpointStyle : { fillStyle : "#456" },
1967
				EndpointStyles : [ null, null ],
1968
				EndpointHoverStyle : null,
1969
				EndpointHoverStyles : [ null, null ],
1970
				HoverPaintStyle : null,
1971
				LabelStyle : { color : "black" },
1972
				LogEnabled : false,
1973
				Overlays : [ ],
1974
				MaxConnections : 1, 
1975
				PaintStyle : { lineWidth : 8, strokeStyle : "#456" },            
1976
				ReattachConnections:false,
1977
				RenderMode : "svg",
1978
				Scope : "jsPlumb_DefaultScope"
1979
			};
1980
			if (_defaults) jsPlumb.extend(this.Defaults, _defaults);
1981
		
1982
			this.logEnabled = this.Defaults.LogEnabled;
1983
			this._connectionTypes = {};
1984
			this._endpointTypes = {};		
1985
1986
			jsPlumbUtil.EventGenerator.apply(this);
1987
1988
			var _currentInstance = this,
1989
				_instanceIndex = getInstanceIndex(),
1990
				_bb = _currentInstance.bind,
1991
				_initialDefaults = {},
1992
	            _zoom = 1,
1993
	            _info = function(el) {
1994
	            	var _el = _dom(el);	
1995
	            	return { el:_el, id:(jsPlumbUtil.isString(el) && _el == null) ? el : _getId(_el) };
1996
	            };
1997
            
1998
	        this.getInstanceIndex = function() { return _instanceIndex; };
1999
2000
        	this.setZoom = function(z, repaintEverything) {
2001
            	_zoom = z;
2002
            	if (repaintEverything) _currentInstance.repaintEverything();
2003
        	};
2004
        	this.getZoom = function() { return _zoom; };
2005
                        
2006
			for (var i in this.Defaults)
2007
				_initialDefaults[i] = this.Defaults[i];
2008
			
2009
			this.bind = function(event, fn) {		
2010
				if ("ready" === event && initialized) fn();
2011
				else _bb.apply(_currentInstance,[event, fn]);
2012
			};
2013
2014
			_currentInstance.importDefaults = function(d) {
2015
				for (var i in d) {
2016
					_currentInstance.Defaults[i] = d[i];
2017
				}	
2018
				return _currentInstance;
2019
			};		
2020
			
2021
			_currentInstance.restoreDefaults = function() {
2022
				_currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults);
2023
				return _currentInstance;
2024
			};
2025
		
2026
		    var log = null,
2027
		        resizeTimer = null,
2028
		        initialized = false,
2029
		        // TODO remove from window scope       
2030
		        connections = [],
2031
		        // map of element id -> endpoint lists. an element can have an arbitrary
2032
		        // number of endpoints on it, and not all of them have to be connected
2033
		        // to anything.         
2034
		        endpointsByElement = {},
2035
		        endpointsByUUID = {},
2036
		        offsets = {},
2037
		        offsetTimestamps = {},
2038
		        floatingConnections = {},
2039
		        draggableStates = {},		
2040
		        connectionBeingDragged = false,
2041
		        sizes = [],
2042
		        _suspendDrawing = false,
2043
		        _suspendedAt = null,
2044
		        DEFAULT_SCOPE = this.Defaults.Scope,
2045
		        renderMode = null,  // will be set in init()		
2046
		        _curIdStamp = 1,
2047
		        _idstamp = function() { return "" + _curIdStamp++; },							
2048
		
2049
				//
2050
				// appends an element to some other element, which is calculated as follows:
2051
				// 
2052
				// 1. if _currentInstance.Defaults.Container exists, use that element.
2053
				// 2. if the 'parent' parameter exists, use that.
2054
				// 3. otherwise just use the root element (for DOM usage, the document body).
2055
				// 
2056
				//
2057
				_appendElement = function(el, parent) {
2058
					if (_currentInstance.Defaults.Container)
2059
						jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container);
2060
					else if (!parent)
2061
						jsPlumbAdapter.appendToRoot(el);
2062
					else
2063
						jsPlumb.CurrentLibrary.appendElement(el, parent);
2064
				},		
2065
				
2066
				//
2067
				// YUI, for some reason, put the result of a Y.all call into an object that contains
2068
				// a '_nodes' array, instead of handing back an array-like object like the other
2069
				// libraries do.
2070
				//
2071
				_convertYUICollection = function(c) {
2072
					return c._nodes ? c._nodes : c;
2073
				},                
2074
2075
			//
2076
			// Draws an endpoint and its connections. this is the main entry point into drawing connections as well
2077
			// as endpoints, since jsPlumb is endpoint-centric under the hood.
2078
			// 
2079
			// @param element element to draw (of type library specific element object)
2080
			// @param ui UI object from current library's event system. optional.
2081
			// @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do.
2082
			// @param clearEdits defaults to false; indicates that mouse edits for connectors should be cleared
2083
			///
2084
			_draw = function(element, ui, timestamp, clearEdits) {
2085
2086
				// TODO is it correct to filter by headless at this top level? how would a headless adapter ever repaint?
2087
	            if (!jsPlumbAdapter.headless && !_suspendDrawing) {
2088
				    var id = _getId(element),
2089
				    	repaintEls = _currentInstance.dragManager.getElementsForDraggable(id);			    
2090
2091
				    if (timestamp == null) timestamp = _timestamp();
2092
2093
				    // update the offset of everything _before_ we try to draw anything.
2094
				    var o = _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp });
2095
2096
			        if (repaintEls) {
2097
			    	    for (var i in repaintEls) {									 							
2098
			    	    	// TODO this seems to cause a lag, but we provide the offset, so in theory it 
2099
			    	    	// should not.  is the timestamp failing?
2100
				    		_updateOffset( { 
2101
				    			elId : repaintEls[i].id, 
2102
				    			offset : {
2103
									left:o.o.left + repaintEls[i].offset.left,
2104
					    			top:o.o.top + repaintEls[i].offset.top
2105
					    		}, 
2106
				    			recalc : false, 
2107
				    			timestamp : timestamp 
2108
				    		});
2109
				    	}
2110
				    }	
2111
				    		          
2112
2113
				    _currentInstance.anchorManager.redraw(id, ui, timestamp, null, clearEdits);
2114
				    
2115
				    if (repaintEls) {
2116
					    for (var j in repaintEls) {
2117
							_currentInstance.anchorManager.redraw(repaintEls[j].id, ui, timestamp, repaintEls[j].offset, clearEdits, true);			    	
2118
					    }
2119
					}		
2120
	            }
2121
			},
2122
2123
			//
2124
			// executes the given function against the given element if the first
2125
			// argument is an object, or the list of elements, if the first argument
2126
			// is a list. the function passed in takes (element, elementId) as
2127
			// arguments.
2128
			//
2129
			_elementProxy = function(element, fn) {
2130
				var retVal = null, el, id;
2131
				if (_ju.isArray(element)) {
2132
					retVal = [];
2133
					for ( var i = 0, j = element.length; i < j; i++) {
2134
						el = _gel(element[i]);
2135
						id = _currentInstance.getAttribute(el, "id");
2136
						retVal.push(fn(el, id)); // append return values to what we will return
2137
					}
2138
				} else {
2139
					el = _gel(element);
2140
					id = _currentInstance.getAttribute(el, "id");
2141
					retVal = fn(el, id);
2142
				}
2143
				return retVal;
2144
			},				
2145
2146
			//
2147
			// gets an Endpoint by uuid.
2148
			//
2149
			_getEndpoint = function(uuid) { return endpointsByUUID[uuid]; },
2150
2151
		/**
2152
		 * inits a draggable if it's not already initialised.
2153
		 * TODO: somehow abstract this to the adapter, because the concept of "draggable" has no
2154
		 * place on the server.
2155
		 */
2156
		_initDraggableIfNecessary = function(element, isDraggable, dragOptions) {
2157
			// TODO move to DragManager?
2158
			if (!jsPlumbAdapter.headless) {
2159
				var _draggable = isDraggable == null ? false : isDraggable, jpcl = jsPlumb.CurrentLibrary;
2160
				if (_draggable) {
2161
					if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) {
2162
						var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions;
2163
						options = jsPlumb.extend( {}, options); // make a copy.
2164
						var dragEvent = jpcl.dragEvents.drag,
2165
							stopEvent = jpcl.dragEvents.stop,
2166
							startEvent = jpcl.dragEvents.start;
2167
	
2168
						options[startEvent] = _ju.wrap(options[startEvent], function() {
2169
							_currentInstance.setHoverSuspended(true);							
2170
							_currentInstance.select({source:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
2171
							_currentInstance.select({target:element}).addClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
2172
							_currentInstance.setConnectionBeingDragged(true);
2173
						});
2174
	
2175
						options[dragEvent] = _ju.wrap(options[dragEvent], function() {                            
2176
							var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom());
2177
							_draw(element, ui, null, true);
2178
							_addClass(element, "jsPlumb_dragged");
2179
						});
2180
						options[stopEvent] = _ju.wrap(options[stopEvent], function() {
2181
							var ui = jpcl.getUIPosition(arguments, _currentInstance.getZoom());
2182
							_draw(element, ui);
2183
							_removeClass(element, "jsPlumb_dragged");
2184
							_currentInstance.setHoverSuspended(false);							
2185
							_currentInstance.select({source:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.sourceElementDraggingClass, true);
2186
							_currentInstance.select({target:element}).removeClass(_currentInstance.elementDraggingClass + " " + _currentInstance.targetElementDraggingClass, true);
2187
							_currentInstance.setConnectionBeingDragged(false);
2188
							_currentInstance.dragManager.dragEnded(element);
2189
						});
2190
						var elId = _getId(element); // need ID
2191
						draggableStates[elId] = true;  
2192
						var draggable = draggableStates[elId];
2193
						options.disabled = draggable == null ? false : !draggable;
2194
						jpcl.initDraggable(element, options, false, _currentInstance);
2195
						_currentInstance.dragManager.register(element);
2196
					}
2197
				}
2198
			}
2199
		},
2200
		
2201
		/*
2202
		* prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc.
2203
		*/
2204
		_prepareConnectionParams = function(params, referenceParams) {
2205
			var _p = jsPlumb.extend( { }, params);
2206
			if (referenceParams) jsPlumb.extend(_p, referenceParams);
2207
			
2208
			// hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively.
2209
			if (_p.source) {
2210
				if (_p.source.endpoint) 
2211
					_p.sourceEndpoint = _p.source;
2212
				else
2213
					_p.source = _dom(_p.source);
2214
			}
2215
			if (_p.target) {
2216
				if (_p.target.endpoint) 
2217
					_p.targetEndpoint = _p.target;
2218
				else
2219
					_p.target = _dom(_p.target);
2220
			}
2221
			
2222
			// test for endpoint uuids to connect
2223
			if (params.uuids) {
2224
				_p.sourceEndpoint = _getEndpoint(params.uuids[0]);
2225
				_p.targetEndpoint = _getEndpoint(params.uuids[1]);
2226
			}						
2227
2228
			// now ensure that if we do have Endpoints already, they're not full.
2229
			// source:
2230
			if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
2231
				_ju.log(_currentInstance, "could not add connection; source endpoint is full");
2232
				return;
2233
			}
2234
2235
			// target:
2236
			if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
2237
				_ju.log(_currentInstance, "could not add connection; target endpoint is full");
2238
				return;
2239
			}
2240
			
2241
			// if source endpoint mandates connection type and nothing specified in our params, use it.
2242
			if (!_p.type && _p.sourceEndpoint)
2243
				_p.type = _p.sourceEndpoint.connectionType;
2244
			
2245
			// copy in any connectorOverlays that were specified on the source endpoint.
2246
			// it doesnt copy target endpoint overlays.  i'm not sure if we want it to or not.
2247
			if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) {
2248
				_p.overlays = _p.overlays || [];
2249
				for (var i = 0, j = _p.sourceEndpoint.connectorOverlays.length; i < j; i++) {
2250
					_p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]);
2251
				}
2252
			}		
2253
            
2254
            // pointer events
2255
            if (!_p["pointer-events"] && _p.sourceEndpoint && _p.sourceEndpoint.connectorPointerEvents)
2256
                _p["pointer-events"] = _p.sourceEndpoint.connectorPointerEvents;
2257
									
2258
			// if there's a target specified (which of course there should be), and there is no
2259
			// target endpoint specified, and 'newConnection' was not set to true, then we check to
2260
			// see if a prior call to makeTarget has provided us with the specs for the target endpoint, and
2261
			// we use those if so.  additionally, if the makeTarget call was specified with 'uniqueEndpoint' set
2262
			// to true, then if that target endpoint has already been created, we re-use it.
2263
2264
			var tid, tep, existingUniqueEndpoint, newEndpoint;
2265
2266
			// TODO: this code can be refactored to be a little dry.
2267
			if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) {
2268
				tid = _getId(_p.target);
2269
				tep =_targetEndpointDefinitions[tid];
2270
				existingUniqueEndpoint = _targetEndpoints[tid];			
2271
2272
				if (tep) {			
2273
					// if target not enabled, return.
2274
					if (!_targetsEnabled[tid]) return;
2275
2276
					// TODO this is dubious. i think it is there so that the endpoint can subsequently
2277
					// be dragged (ie it kicks off the draggable registration). but it is dubious.
2278
					tep.isTarget = true;
2279
2280
					// check for max connections??						
2281
					newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep);
2282
					if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint;
2283
					 _p.targetEndpoint = newEndpoint;
2284
					 // TODO test options to makeTarget to see if we should do this?
2285
					 newEndpoint._doNotDeleteOnDetach = false; // reset.
2286
					 newEndpoint._deleteOnDetach = true;					 
2287
				}
2288
			}
2289
2290
			// same thing, but for source.
2291
			if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) {
2292
				tid = _getId(_p.source);
2293
				tep = _sourceEndpointDefinitions[tid];
2294
				existingUniqueEndpoint = _sourceEndpoints[tid];				
2295
2296
				if (tep) {
2297
					// if source not enabled, return.					
2298
					if (!_sourcesEnabled[tid]) return;
2299
2300
					// TODO this is dubious. i think it is there so that the endpoint can subsequently
2301
					// be dragged (ie it kicks off the draggable registration). but it is dubious.
2302
					//tep.isSource = true;
2303
				
2304
					newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep);
2305
					if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint;
2306
					 _p.sourceEndpoint = newEndpoint;
2307
					 // TODO test options to makeSource to see if we should do this?
2308
					 newEndpoint._doNotDeleteOnDetach = false; // reset.
2309
					 newEndpoint._deleteOnDetach = true;
2310
				}
2311
			}
2312
			
2313
			return _p;
2314
		},
2315
		
2316
		_newConnection = function(params) {
2317
			var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
2318
			    endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint,
2319
			    parent = jsPlumb.CurrentLibrary.getParent;
2320
			
2321
			if (params.container)
2322
				params.parent = params.container;
2323
			else {
2324
				if (params.sourceEndpoint)
2325
					params.parent = params.sourceEndpoint.parent;
2326
				else if (params.source.constructor == endpointFunc)
2327
					params.parent = params.source.parent;
2328
				else params.parent = parent(params.source);
2329
			}
2330
			
2331
			params._jsPlumb = _currentInstance;
2332
            params.newConnection = _newConnection;
2333
            params.newEndpoint = _newEndpoint;
2334
            params.endpointsByUUID = endpointsByUUID;             
2335
            params.endpointsByElement = endpointsByElement;  
2336
            params.finaliseConnection = _finaliseConnection;
2337
			var con = new connectionFunc(params);
2338
			con.id = "con_" + _idstamp();
2339
			_eventFireProxy("click", "click", con);
2340
			_eventFireProxy("dblclick", "dblclick", con);
2341
            _eventFireProxy("contextmenu", "contextmenu", con);
2342
2343
            // if the connection is draggable, then maybe we need to tell the target endpoint to init the
2344
            // dragging code. it won't run again if it already configured to be draggable.
2345
            if (con.isDetachable()) {
2346
            	con.endpoints[0].initDraggable();
2347
            	con.endpoints[1].initDraggable();
2348
            }
2349
2350
			return con;
2351
		},
2352
		
2353
		//
2354
		// adds the connection to the backing model, fires an event if necessary and then redraws
2355
		//
2356
		_finaliseConnection = function(jpc, params, originalEvent, doInformAnchorManager) {
2357
            params = params || {};
2358
			// add to list of connections (by scope).
2359
            if (!jpc.suspendedEndpoint)
2360
			    connections.push(jpc);
2361
			
2362
            // always inform the anchor manager
2363
            // except that if jpc has a suspended endpoint it's not true to say the
2364
            // connection is new; it has just (possibly) moved. the question is whether
2365
            // to make that call here or in the anchor manager.  i think perhaps here.
2366
            if (jpc.suspendedEndpoint == null || doInformAnchorManager)
2367
            	_currentInstance.anchorManager.newConnection(jpc);
2368
2369
			// force a paint
2370
			_draw(jpc.source);
2371
			
2372
			// fire an event
2373
			if (!params.doNotFireConnectionEvent && params.fireEvent !== false) {
2374
			
2375
				var eventArgs = {
2376
					connection:jpc,
2377
					source : jpc.source, target : jpc.target,
2378
					sourceId : jpc.sourceId, targetId : jpc.targetId,
2379
					sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
2380
				};
2381
			
2382
				_currentInstance.fire("connection", eventArgs, originalEvent);
2383
			}
2384
		},
2385
		
2386
		_eventFireProxy = function(event, proxyEvent, obj) {
2387
			obj.bind(event, function(originalObject, originalEvent) {
2388
				_currentInstance.fire(proxyEvent, obj, originalEvent);
2389
			});
2390
		},
2391
		
2392
		/*
2393
		 * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added.
2394
		 * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb.
2395
		 * 
2396
		 *   the logic is to first look for a "container" member of params, and pass that back if found.  otherwise we
2397
		 *   handoff to the 'getParent' function in the current library.
2398
		 */
2399
		_getParentFromParams = function(params) {
2400
			if (params.container)
2401
				return params.container;
2402
			else {
2403
                var tag = jsPlumb.CurrentLibrary.getTagName(params.source),
2404
                    p = jsPlumb.CurrentLibrary.getParent(params.source);
2405
                if (tag && tag.toLowerCase() === "td")
2406
                    return jsPlumb.CurrentLibrary.getParent(p);
2407
                else return p;
2408
            }
2409
		},
2410
		
2411
		/*
2412
			factory method to prepare a new endpoint.  this should always be used instead of creating Endpoints
2413
			manually, since this method attaches event listeners and an id.
2414
		*/
2415
		_newEndpoint = function(params) {
2416
				var endpointFunc = _currentInstance.Defaults.EndpointType || jsPlumb.Endpoint;
2417
				var _p = jsPlumb.extend({}, params);				
2418
				_p.parent = _getParentFromParams(_p);
2419
				_p._jsPlumb = _currentInstance;
2420
                _p.newConnection = _newConnection;
2421
                _p.newEndpoint = _newEndpoint;                
2422
                _p.endpointsByUUID = endpointsByUUID;             
2423
                _p.endpointsByElement = endpointsByElement;  
2424
                _p.finaliseConnection = _finaliseConnection;
2425
                _p.fireDetachEvent = fireDetachEvent;
2426
                _p.fireMoveEvent = fireMoveEvent;
2427
                _p.floatingConnections = floatingConnections;
2428
                _p.getParentFromParams = _getParentFromParams;
2429
                _p.elementId = _getId(_p.source);                
2430
				var ep = new endpointFunc(_p);			
2431
				ep.id = "ep_" + _idstamp();
2432
				_eventFireProxy("click", "endpointClick", ep);
2433
				_eventFireProxy("dblclick", "endpointDblClick", ep);
2434
				_eventFireProxy("contextmenu", "contextmenu", ep);
2435
				if (!jsPlumbAdapter.headless)
2436
					_currentInstance.dragManager.endpointAdded(_p.source);
2437
			return ep;
2438
		},
2439
		
2440
		/*
2441
		 * performs the given function operation on all the connections found
2442
		 * for the given element id; this means we find all the endpoints for
2443
		 * the given element, and then for each endpoint find the connectors
2444
		 * connected to it. then we pass each connection in to the given
2445
		 * function.		 
2446
		 */
2447
		_operation = function(elId, func, endpointFunc) {
2448
			var endpoints = endpointsByElement[elId];
2449
			if (endpoints && endpoints.length) {
2450
				for ( var i = 0, ii = endpoints.length; i < ii; i++) {
2451
					for ( var j = 0, jj = endpoints[i].connections.length; j < jj; j++) {
2452
						var retVal = func(endpoints[i].connections[j]);
2453
						// if the function passed in returns true, we exit.
2454
						// most functions return false.
2455
						if (retVal) return;
2456
					}
2457
					if (endpointFunc) endpointFunc(endpoints[i]);
2458
				}
2459
			}
2460
		},	
2461
		
2462
		_setDraggable = function(element, draggable) {
2463
			return _elementProxy(element, function(el, id) {
2464
				draggableStates[id] = draggable;
2465
				if (jsPlumb.CurrentLibrary.isDragSupported(el)) {
2466
					jsPlumb.CurrentLibrary.setDraggable(el, draggable);
2467
				}
2468
			});
2469
		},
2470
		/*
2471
		 * private method to do the business of hiding/showing.
2472
		 * 
2473
		 * @param el
2474
		 *            either Id of the element in question or a library specific
2475
		 *            object for the element.
2476
		 * @param state
2477
		 *            String specifying a value for the css 'display' property
2478
		 *            ('block' or 'none').
2479
		 */
2480
		_setVisible = function(el, state, alsoChangeEndpoints) {
2481
			state = state === "block";
2482
			var endpointFunc = null;
2483
			if (alsoChangeEndpoints) {
2484
				if (state) endpointFunc = function(ep) {
2485
					ep.setVisible(true, true, true);
2486
				};
2487
				else endpointFunc = function(ep) {
2488
					ep.setVisible(false, true, true);
2489
				};
2490
			}
2491
			var info = _info(el);
2492
			_operation(info.id, function(jpc) {
2493
				if (state && alsoChangeEndpoints) {		
2494
					// this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility.
2495
					// this block will only set a connection to be visible if the other endpoint in the connection is also visible.
2496
					var oidx = jpc.sourceId === info.id ? 1 : 0;
2497
					if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true);
2498
				}
2499
				else  // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever.
2500
					jpc.setVisible(state);
2501
			}, endpointFunc);
2502
		},
2503
		/*
2504
		 * toggles the draggable state of the given element(s).
2505
		 * el is either an id, or an element object, or a list of ids/element objects.
2506
		 */
2507
		_toggleDraggable = function(el) {
2508
			return _elementProxy(el, function(el, elId) {
2509
				var state = draggableStates[elId] == null ? false : draggableStates[elId];
2510
				state = !state;
2511
				draggableStates[elId] = state;
2512
				jsPlumb.CurrentLibrary.setDraggable(el, state);
2513
				return state;
2514
			});
2515
		},
2516
		/**
2517
		 * private method to do the business of toggling hiding/showing.
2518
		 */
2519
		_toggleVisible = function(elId, changeEndpoints) {
2520
			var endpointFunc = null;
2521
			if (changeEndpoints) {
2522
				endpointFunc = function(ep) {
2523
					var state = ep.isVisible();
2524
					ep.setVisible(!state);
2525
				};
2526
			}
2527
			_operation(elId, function(jpc) {
2528
				var state = jpc.isVisible();
2529
				jpc.setVisible(!state);				
2530
			}, endpointFunc);
2531
			// todo this should call _elementProxy, and pass in the
2532
			// _operation(elId, f) call as a function. cos _toggleDraggable does
2533
			// that.
2534
		},
2535
		/**
2536
		 * updates the offset and size for a given element, and stores the
2537
		 * values. if 'offset' is not null we use that (it would have been
2538
		 * passed in from a drag call) because it's faster; but if it is null,
2539
		 * or if 'recalc' is true in order to force a recalculation, we get the current values.
2540
		 */
2541
		_updateOffset = function(params) {
2542
			var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId, s;
2543
			if (_suspendDrawing && !timestamp) timestamp = _suspendedAt;
2544
			if (!recalc) {
2545
				if (timestamp && timestamp === offsetTimestamps[elId]) {			
2546
					return {o:params.offset || offsets[elId], s:sizes[elId]};
2547
				}
2548
			}			
2549
			if (recalc || !offset) { // if forced repaint or no offset available, we recalculate.
2550
				// get the current size and offset, and store them
2551
				s = _gel(elId);
2552
				if (s != null) {						
2553
					sizes[elId] = _getSize(s);
2554
					offsets[elId] = _getOffset(s, _currentInstance);
2555
					offsetTimestamps[elId] = timestamp;
2556
				}
2557
			} else {
2558
				offsets[elId] = offset;
2559
                if (sizes[elId] == null) {
2560
                    s = _gel(elId);
2561
                    if (s != null) sizes[elId] = _getSize(s);
2562
                }
2563
                offsetTimestamps[elId] = timestamp;
2564
            }
2565
			
2566
			if(offsets[elId] && !offsets[elId].right) {
2567
				offsets[elId].right = offsets[elId].left + sizes[elId][0];
2568
				offsets[elId].bottom = offsets[elId].top + sizes[elId][1];	
2569
				offsets[elId].width = sizes[elId][0];
2570
				offsets[elId].height = sizes[elId][1];	
2571
				offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2);
2572
				offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2);				
2573
			}
2574
			return {o:offsets[elId], s:sizes[elId]};
2575
		},
2576
2577
		// TODO comparison performance
2578
		_getCachedData = function(elId) {
2579
			var o = offsets[elId];
2580
			if (!o) 
2581
                return _updateOffset({elId:elId});
2582
			else
2583
                return {o:o, s:sizes[elId]};
2584
		},
2585
2586
		/**
2587
		 * gets an id for the given element, creating and setting one if
2588
		 * necessary.  the id is of the form
2589
		 *
2590
		 *	jsPlumb_<instance index>_<index in instance>
2591
		 *
2592
		 * where "index in instance" is a monotonically increasing integer that starts at 0,
2593
		 * for each instance.  this method is used not only to assign ids to elements that do not
2594
		 * have them but also to connections and endpoints.
2595
		 */
2596
		_getId = function(element, uuid, doNotCreateIfNotFound) {
2597
			if (jsPlumbUtil.isString(element)) return element;			
2598
			if (element == null) return null;			
2599
			var id = jsPlumbAdapter.getAttribute(element, "id");
2600
			if (!id || id === "undefined") {
2601
				// check if fixed uuid parameter is given
2602
				if (arguments.length == 2 && arguments[1] !== undefined)
2603
					id = uuid;
2604
				else if (arguments.length == 1 || (arguments.length == 3 && !arguments[2]))
2605
					id = "jsPlumb_" + _instanceIndex + "_" + _idstamp();
2606
				
2607
                if (!doNotCreateIfNotFound) jsPlumbAdapter.setAttribute(element, "id", id);
2608
			}
2609
			return id;
2610
		};
2611
2612
		this.setConnectionBeingDragged = function(v) {
2613
			connectionBeingDragged = v;
2614
		};
2615
		this.isConnectionBeingDragged = function() {
2616
			return connectionBeingDragged;
2617
		};
2618
    
2619
		this.connectorClass = "_jsPlumb_connector";            		
2620
		this.hoverClass = "_jsPlumb_hover";            		
2621
		this.endpointClass = "_jsPlumb_endpoint";		
2622
		this.endpointConnectedClass = "_jsPlumb_endpoint_connected";		
2623
		this.endpointFullClass = "_jsPlumb_endpoint_full";		
2624
		this.endpointDropAllowedClass = "_jsPlumb_endpoint_drop_allowed";		
2625
		this.endpointDropForbiddenClass = "_jsPlumb_endpoint_drop_forbidden";		
2626
		this.overlayClass = "_jsPlumb_overlay";				
2627
		this.draggingClass = "_jsPlumb_dragging";		
2628
		this.elementDraggingClass = "_jsPlumb_element_dragging";			
2629
		this.sourceElementDraggingClass = "_jsPlumb_source_element_dragging";
2630
		this.targetElementDraggingClass = "_jsPlumb_target_element_dragging";
2631
		this.endpointAnchorClassPrefix = "_jsPlumb_endpoint_anchor";
2632
		this.hoverSourceClass = "_jsPlumb_source_hover";	
2633
		this.hoverTargetClass = "_jsPlumb_target_hover";
2634
		this.dragSelectClass = "_jsPlumb_drag_select";
2635
2636
		this.Anchors = {};		
2637
		this.Connectors = {  "canvas":{}, "svg":{}, "vml":{} };				
2638
		this.Endpoints = { "canvas":{}, "svg":{}, "vml":{} };
2639
		this.Overlays = { "canvas":{}, "svg":{}, "vml":{}};		
2640
		this.ConnectorRenderers = {};				
2641
		this.SVG = "svg";
2642
		this.CANVAS = "canvas";		
2643
		this.VML = "vml";
2644
				
2645
2646
// --------------------------- jsPLumbInstance public API ---------------------------------------------------------
2647
					
2648
		
2649
		this.addEndpoint = function(el, params, referenceParams) {
2650
			referenceParams = referenceParams || {};
2651
			var p = jsPlumb.extend({}, referenceParams);
2652
			jsPlumb.extend(p, params);
2653
			p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint;
2654
			p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
2655
            // YUI wrapper
2656
			el = _convertYUICollection(el);							
2657
2658
			var results = [], 
2659
				inputs = (_ju.isArray(el) || (el.length != null && !_ju.isString(el))) ? el : [ el ];
2660
						
2661
			for (var i = 0, j = inputs.length; i < j; i++) {
2662
				var _el = _dom(inputs[i]), id = _getId(_el);
2663
				p.source = _el;
2664
2665
                _updateOffset({ elId : id, timestamp:_suspendedAt });
2666
				var e = _newEndpoint(p);
2667
				if (p.parentAnchor) e.parentAnchor = p.parentAnchor;
2668
				_ju.addToList(endpointsByElement, id, e);
2669
				var myOffset = offsets[id], 
2670
					myWH = sizes[id],
2671
					anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e, timestamp:_suspendedAt }),
2672
					endpointPaintParams = { anchorLoc : anchorLoc, timestamp:_suspendedAt };
2673
				
2674
				if (_suspendDrawing) endpointPaintParams.recalc = false;
2675
				if (!_suspendDrawing) e.paint(endpointPaintParams);
2676
				
2677
				results.push(e);
2678
				e._doNotDeleteOnDetach = true; // mark this as being added via addEndpoint.				
2679
			}
2680
			
2681
			return results.length == 1 ? results[0] : results;
2682
		};
2683
		
2684
		
2685
		this.addEndpoints = function(el, endpoints, referenceParams) {
2686
			var results = [];
2687
			for ( var i = 0, j = endpoints.length; i < j; i++) {
2688
				var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams);
2689
				if (_ju.isArray(e))
2690
					Array.prototype.push.apply(results, e);
2691
				else results.push(e);
2692
			}
2693
			return results;
2694
		};
2695
		
2696
		this.animate = function(el, properties, options) {
2697
			options = options || {};
2698
			var ele = _gel(el), 
2699
				id = _getId(el),
2700
				stepFunction = jsPlumb.CurrentLibrary.dragEvents.step,
2701
				completeFunction = jsPlumb.CurrentLibrary.dragEvents.complete;
2702
2703
			options[stepFunction] = _ju.wrap(options[stepFunction], function() {
2704
				_currentInstance.repaint(id);
2705
			});
2706
2707
			// onComplete repaints, just to make sure everything looks good at the end of the animation.
2708
			options[completeFunction] = _ju.wrap(options[completeFunction], function() {
2709
				_currentInstance.repaint(id);
2710
			});
2711
2712
			jsPlumb.CurrentLibrary.animate(ele, properties, options);
2713
		};		
2714
		
2715
		/**
2716
		* checks for a listener for the given condition, executing it if found, passing in the given value.
2717
		* condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since
2718
		* firing click events etc is a bit different to what this does).  i thought about adding a "bindCondition"
2719
		* or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these
2720
		* condition events anyway.
2721
		*/
2722
		this.checkCondition = function(conditionName, value) {
2723
			var l = _currentInstance.getListener(conditionName),
2724
				r = true;
2725
				
2726
			if (l && l.length > 0) {
2727
				try {
2728
					for (var i = 0, j = l.length; i < j; i++) {
2729
						r = r && l[i](value); 
2730
					}
2731
				}
2732
				catch (e) { 
2733
					_ju.log(_currentInstance, "cannot check condition [" + conditionName + "]" + e); 
2734
				}
2735
			}
2736
			return r;
2737
		};
2738
		
2739
		/**
2740
		 * checks a condition asynchronously: fires the event handler and passes the handler
2741
		 * a 'proceed' function and a 'stop' function. The handler MUST execute one or other
2742
		 * of these once it has made up its mind.
2743
		 *
2744
		 * Note that although this reads the listener list for the given condition, it
2745
		 * does not loop through and hit each listener, because that, with asynchronous
2746
		 * callbacks, would be messy. so it uses only the first listener registered.
2747
		 */ 
2748
		this.checkASyncCondition = function(conditionName, value, proceed, stop) {
2749
			var l = _currentInstance.getListener(conditionName);
2750
				
2751
			if (l && l.length > 0) {
2752
				try {
2753
					l[0](value, proceed, stop); 					
2754
				}
2755
				catch (e) { 
2756
					_ju.log(_currentInstance, "cannot asynchronously check condition [" + conditionName + "]" + e); 
2757
				}
2758
			}	
2759
		};
2760
2761
		
2762
		this.connect = function(params, referenceParams) {
2763
			// prepare a final set of parameters to create connection with
2764
			var _p = _prepareConnectionParams(params, referenceParams), jpc;
2765
			// TODO probably a nicer return value if the connection was not made.  _prepareConnectionParams
2766
			// will return null (and log something) if either endpoint was full.  what would be nicer is to 
2767
			// create a dedicated 'error' object.
2768
			if (_p) {
2769
				// create the connection.  it is not yet registered 
2770
				jpc = _newConnection(_p);
2771
				// now add it the model, fire an event, and redraw
2772
				_finaliseConnection(jpc, _p);										
2773
			}
2774
			return jpc;
2775
		};		
2776
		
2777
		this.deleteEndpoint = function(object, doNotRepaintAfterwards) {
2778
			var _is = _currentInstance.setSuspendDrawing(true);
2779
			var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object;			
2780
			if (endpoint) {		
2781
				_currentInstance.deleteObject({
2782
					endpoint:endpoint
2783
				});
2784
			}
2785
			if(!_is) _currentInstance.setSuspendDrawing(false, doNotRepaintAfterwards);
2786
			return _currentInstance;									
2787
		};		
2788
		
2789
		this.deleteEveryEndpoint = function() {
2790
			var _is = _currentInstance.setSuspendDrawing(true);
2791
			for ( var id in endpointsByElement) {
2792
				var endpoints = endpointsByElement[id];
2793
				if (endpoints && endpoints.length) {
2794
					for ( var i = 0, j = endpoints.length; i < j; i++) {
2795
						_currentInstance.deleteEndpoint(endpoints[i], true);
2796
					}
2797
				}
2798
			}			
2799
			endpointsByElement = {};			
2800
			endpointsByUUID = {};
2801
			_currentInstance.anchorManager.reset();
2802
			_currentInstance.dragManager.reset();							
2803
			if(!_is) _currentInstance.setSuspendDrawing(false);
2804
			return _currentInstance;
2805
		};
2806
2807
		var fireDetachEvent = function(jpc, doFireEvent, originalEvent) {
2808
            // may have been given a connection, or in special cases, an object
2809
            var connType =  _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
2810
                argIsConnection = jpc.constructor == connType,
2811
                params = argIsConnection ? {
2812
                    connection:jpc,
2813
				    source : jpc.source, target : jpc.target,
2814
				    sourceId : jpc.sourceId, targetId : jpc.targetId,
2815
				    sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
2816
                } : jpc;
2817
2818
			if (doFireEvent)
2819
				_currentInstance.fire("connectionDetached", params, originalEvent);
2820
			
2821
            _currentInstance.anchorManager.connectionDetached(params);
2822
		};	
2823
2824
		var fireMoveEvent = function(params, evt) {
2825
			_currentInstance.fire("connectionMoved", params, evt);
2826
		};
2827
2828
		this.unregisterEndpoint = function(endpoint) {
2829
			if (endpoint._jsPlumb.uuid) endpointsByUUID[endpoint._jsPlumb.uuid] = null;				
2830
			_currentInstance.anchorManager.deleteEndpoint(endpoint);			
2831
			// TODO at least replace this with a removeWithFunction call.			
2832
			for (var e in endpointsByElement) {
2833
				var endpoints = endpointsByElement[e];
2834
				if (endpoints) {
2835
					var newEndpoints = [];
2836
					for (var i = 0, j = endpoints.length; i < j; i++)
2837
						if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]);
2838
					
2839
					endpointsByElement[e] = newEndpoints;
2840
				}
2841
				if(endpointsByElement[e].length <1){
2842
					delete endpointsByElement[e];
2843
				}
2844
			}
2845
		};
2846
				
2847
		this.detach = function() {
2848
2849
            if (arguments.length === 0) return;
2850
            var connType =  _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
2851
                firstArgIsConnection = arguments[0].constructor == connType,
2852
                params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0],
2853
                fireEvent = (params.fireEvent !== false),
2854
                forceDetach = params.forceDetach,
2855
                conn = firstArgIsConnection ? arguments[0] : params.connection;
2856
                                                    
2857
				if (conn) {             
2858
                    if (forceDetach || jsPlumbUtil.functionChain(true, false, [
2859
                            [ conn.endpoints[0], "isDetachAllowed", [ conn ] ],    
2860
                            [ conn.endpoints[1], "isDetachAllowed", [ conn ] ],
2861
                            [ conn, "isDetachAllowed", [ conn ] ],
2862
                            [ _currentInstance, "checkCondition", [ "beforeDetach", conn ] ] ])) {
2863
                        
2864
                        conn.endpoints[0].detach(conn, false, true, fireEvent); 
2865
                    }
2866
                }
2867
                else {
2868
					var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case.
2869
					// test for endpoint uuids to detach
2870
					if (_p.uuids) {
2871
						_getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent);
2872
					} else if (_p.sourceEndpoint && _p.targetEndpoint) {
2873
						_p.sourceEndpoint.detachFrom(_p.targetEndpoint);
2874
					} else {
2875
						var sourceId = _getId(_dom(_p.source)),
2876
						    targetId = _getId(_dom(_p.target));
2877
						_operation(sourceId, function(jpc) {
2878
						    if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
2879
							    if (_currentInstance.checkCondition("beforeDetach", jpc)) {
2880
                                    jpc.endpoints[0].detach(jpc, false, true, fireEvent);
2881
								}
2882
							}
2883
						});
2884
					}
2885
				}
2886
		};
2887
2888
		this.detachAllConnections = function(el, params) {
2889
            params = params || {};
2890
            el = _dom(el);
2891
			var id = _getId(el),
2892
                endpoints = endpointsByElement[id];
2893
			if (endpoints && endpoints.length) {
2894
				for ( var i = 0, j = endpoints.length; i < j; i++) {
2895
					endpoints[i].detachAll(params.fireEvent !== false);
2896
				}
2897
			}
2898
			return _currentInstance;
2899
		};
2900
2901
		this.detachEveryConnection = function(params) {
2902
            params = params || {};
2903
            _currentInstance.doWhileSuspended(function() {
2904
				for ( var id in endpointsByElement) {
2905
					var endpoints = endpointsByElement[id];
2906
					if (endpoints && endpoints.length) {
2907
						for ( var i = 0, j = endpoints.length; i < j; i++) {
2908
							endpoints[i].detachAll(params.fireEvent !== false);
2909
						}
2910
					}
2911
				}
2912
				connections.splice(0);
2913
			});
2914
			return _currentInstance;
2915
		};
2916
2917
		/// not public.  but of course its exposed. how to change this.
2918
		this.deleteObject = function(params) {
2919
			var result = {
2920
					endpoints : {}, 
2921
					connections : {},
2922
					endpointCount:0,
2923
					connectionCount:0
2924
				},
2925
				fireEvent = params.fireEvent !== false,
2926
				deleteAttachedObjects = params.deleteAttachedObjects !== false;
2927
2928
			var unravelConnection = function(connection) {
2929
				if(connection != null && result.connections[connection.id] == null) {
2930
					if (connection._jsPlumb != null) connection.setHover(false);
2931
					result.connections[connection.id] = connection;
2932
					result.connectionCount++;
2933
					if (deleteAttachedObjects) {
2934
						for (var j = 0; j < connection.endpoints.length; j++) {
2935
							if (connection.endpoints[j]._deleteOnDetach)
2936
								unravelEndpoint(connection.endpoints[j]);
2937
						}
2938
					}					
2939
				}
2940
			};
2941
			var unravelEndpoint = function(endpoint) {
2942
				if(endpoint != null && result.endpoints[endpoint.id] == null) {
2943
					if (endpoint._jsPlumb != null) endpoint.setHover(false);
2944
					result.endpoints[endpoint.id] = endpoint;
2945
					result.endpointCount++;
2946
2947
					if (deleteAttachedObjects) {
2948
						for (var i = 0; i < endpoint.connections.length; i++) {
2949
							var c = endpoint.connections[i];
2950
							unravelConnection(c);						
2951
						}
2952
					}
2953
				}
2954
			};
2955
2956
			if (params.connection) 
2957
				unravelConnection(params.connection);
2958
			else unravelEndpoint(params.endpoint);
2959
2960
			// loop through connections
2961
			for (var i in result.connections) {
2962
				var c = result.connections[i];
2963
				c.endpoints[0].detachFromConnection(c);
2964
				c.endpoints[1].detachFromConnection(c);
2965
				//_currentInstance.unregisterConnection(c);
2966
				jsPlumbUtil.removeWithFunction(connections, function(_c) {
2967
				    return c.id == _c.id;
2968
				});
2969
				fireDetachEvent(c, fireEvent, params.originalEvent);
2970
				c.cleanup();
2971
				c.destroy();
2972
			}
2973
2974
			// loop through endpoints
2975
			for (var j in result.endpoints) {
2976
				var e = result.endpoints[j];	
2977
				_currentInstance.unregisterEndpoint(e);
2978
				// FIRE some endpoint deleted event?
2979
				e.cleanup();
2980
				e.destroy();
2981
			}	
2982
2983
			return result;
2984
		};
2985
 
2986
		this.draggable = function(el, options) {
2987
			var i,j,ele;
2988
			// allows for array or jquery/mootools selector
2989
			if (typeof el == 'object' && el.length) {
2990
				for (i = 0, j = el.length; i < j; i++) {
2991
					ele = _dom(el[i]);
2992
					if (ele) _initDraggableIfNecessary(ele, true, options);
2993
				}
2994
			} 
2995
			// allows for YUI selector
2996
			else if (el._nodes) { 	// TODO this is YUI specific; really the logic should be forced
2997
				// into the library adapters (for jquery and mootools aswell)
2998
				for (i = 0, j = el._nodes.length; i < j; i++) {
2999
					ele = _dom(el._nodes[i]);
3000
					if (ele) _initDraggableIfNecessary(ele, true, options);
3001
				}
3002
			}
3003
			else {				
3004
				ele = _dom(el);
3005
				if (ele) _initDraggableIfNecessary(ele, true, options);
3006
			}
3007
			return _currentInstance;
3008
		};
3009
3010
3011
		// just a library-agnostic wrapper.
3012
		this.extend = function(o1, o2) {
3013
			return jsPlumb.CurrentLibrary.extend(o1, o2);
3014
		};
3015
3016
		// helpers for select/selectEndpoints
3017
		var _setOperation = function(list, func, args, selector) {
3018
				for (var i = 0, j = list.length; i < j; i++) {
3019
					list[i][func].apply(list[i], args);
3020
				}	
3021
				return selector(list);
3022
			},
3023
			_getOperation = function(list, func, args) {
3024
				var out = [];
3025
				for (var i = 0, j = list.length; i < j; i++) {					
3026
					out.push([ list[i][func].apply(list[i], args), list[i] ]);
3027
				}	
3028
				return out;
3029
			},
3030
			setter = function(list, func, selector) {
3031
				return function() {
3032
					return _setOperation(list, func, arguments, selector);
3033
				};
3034
			},
3035
			getter = function(list, func) {
3036
				return function() {
3037
					return _getOperation(list, func, arguments);
3038
				};	
3039
			},
3040
			prepareList = function(input, doNotGetIds) {
3041
				var r = [];
3042
				if (input) {
3043
					if (typeof input == 'string') {
3044
						if (input === "*") return input;
3045
						r.push(input);
3046
					}
3047
					else {
3048
						input = _gel(input);
3049
						if (doNotGetIds) r = input;
3050
						else { 
3051
							for (var i = 0, j = input.length; i < j; i++) 
3052
								r.push(_info(input[i]).id);
3053
						}	
3054
					}
3055
				}
3056
				return r;
3057
			},
3058
			filterList = function(list, value, missingIsFalse) {
3059
				if (list === "*") return true;
3060
				return list.length > 0 ? jsPlumbUtil.indexOf(list, value) != -1 : !missingIsFalse;
3061
			};
3062
3063
		// get some connections, specifying source/target/scope
3064
		this.getConnections = function(options, flat) {
3065
			if (!options) {
3066
				options = {};
3067
			} else if (options.constructor == String) {
3068
				options = { "scope": options };
3069
			}
3070
			var scope = options.scope || _currentInstance.getDefaultScope(),
3071
				scopes = prepareList(scope, true),
3072
				sources = prepareList(options.source),
3073
				targets = prepareList(options.target),			
3074
				results = (!flat && scopes.length > 1) ? {} : [],
3075
				_addOne = function(scope, obj) {
3076
					if (!flat && scopes.length > 1) {
3077
						var ss = results[scope];
3078
						if (ss == null) {
3079
							ss = results[scope] = [];
3080
						}
3081
						ss.push(obj);
3082
					} else results.push(obj);
3083
				};
3084
			
3085
			for ( var j = 0, jj = connections.length; j < jj; j++) {
3086
				var c = connections[j];
3087
				if (filterList(scopes, c.scope) && filterList(sources, c.sourceId) && filterList(targets, c.targetId))
3088
					_addOne(c.scope, c);
3089
			}
3090
			
3091
			return results;
3092
		};
3093
		
3094
		var _curryEach = function(list, executor) {
3095
				return function(f) {
3096
					for (var i = 0, ii = list.length; i < ii; i++) {
3097
						f(list[i]);
3098
					}
3099
					return executor(list);
3100
				};		
3101
			},
3102
			_curryGet = function(list) {
3103
				return function(idx) {
3104
					return list[idx];
3105
				};
3106
			};
3107
			
3108
		var _makeCommonSelectHandler = function(list, executor) {
3109
            var out = {
3110
                    length:list.length,
3111
				    each:_curryEach(list, executor),
3112
				    get:_curryGet(list)
3113
                },
3114
                setters = ["setHover", "removeAllOverlays", "setLabel", "addClass", "addOverlay", "removeOverlay", 
3115
                           "removeOverlays", "showOverlay", "hideOverlay", "showOverlays", "hideOverlays", "setPaintStyle",
3116
                           "setHoverPaintStyle", "setSuspendEvents", "setParameter", "setParameters", "setVisible", 
3117
                           "repaint", "addType", "toggleType", "removeType", "removeClass", "setType", "bind", "unbind" ],
3118
                
3119
                getters = ["getLabel", "getOverlay", "isHover", "getParameter", "getParameters", "getPaintStyle",
3120
                           "getHoverPaintStyle", "isVisible", "hasType", "getType", "isSuspendEvents" ],
3121
                i, ii;
3122
            
3123
            for (i = 0, ii = setters.length; i < ii; i++)
3124
                out[setters[i]] = setter(list, setters[i], executor);
3125
            
3126
            for (i = 0, ii = getters.length; i < ii; i++)
3127
                out[getters[i]] = getter(list, getters[i]);       
3128
            
3129
            return out;
3130
		};
3131
		
3132
		var	_makeConnectionSelectHandler = function(list) {
3133
			var common = _makeCommonSelectHandler(list, _makeConnectionSelectHandler);
3134
			return jsPlumb.CurrentLibrary.extend(common, {
3135
				// setters									
3136
				setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler),
3137
				setReattach:setter(list, "setReattach", _makeConnectionSelectHandler),
3138
				setConnector:setter(list, "setConnector", _makeConnectionSelectHandler),			
3139
				detach:function() {
3140
					for (var i = 0, ii = list.length; i < ii; i++)
3141
						_currentInstance.detach(list[i]);
3142
				},				
3143
				// getters
3144
				isDetachable:getter(list, "isDetachable"),
3145
				isReattach:getter(list, "isReattach")
3146
			});
3147
		};
3148
		
3149
		var	_makeEndpointSelectHandler = function(list) {
3150
			var common = _makeCommonSelectHandler(list, _makeEndpointSelectHandler);
3151
			return jsPlumb.CurrentLibrary.extend(common, {
3152
				setEnabled:setter(list, "setEnabled", _makeEndpointSelectHandler),				
3153
				setAnchor:setter(list, "setAnchor", _makeEndpointSelectHandler),
3154
				isEnabled:getter(list, "isEnabled"),
3155
				detachAll:function() {
3156
					for (var i = 0, ii = list.length; i < ii; i++)
3157
						list[i].detachAll();
3158
				},
3159
				"remove":function() {
3160
					for (var i = 0, ii = list.length; i < ii; i++)
3161
						_currentInstance.deleteObject({endpoint:list[i]});
3162
				}
3163
			});
3164
		};
3165
			
3166
3167
		this.select = function(params) {
3168
			params = params || {};
3169
			params.scope = params.scope || "*";
3170
			return _makeConnectionSelectHandler(params.connections || _currentInstance.getConnections(params, true));							
3171
		};		
3172
3173
		this.selectEndpoints = function(params) {
3174
			params = params || {};
3175
			params.scope = params.scope || "*";
3176
			var noElementFilters = !params.element && !params.source && !params.target,			
3177
				elements = noElementFilters ? "*" : prepareList(params.element),
3178
				sources = noElementFilters ? "*" : prepareList(params.source),
3179
				targets = noElementFilters ? "*" : prepareList(params.target),
3180
				scopes = prepareList(params.scope, true);
3181
			
3182
			var ep = [];
3183
			
3184
			for (var el in endpointsByElement) {
3185
				var either = filterList(elements, el, true),
3186
					source = filterList(sources, el, true),
3187
					sourceMatchExact = sources != "*",
3188
					target = filterList(targets, el, true),
3189
					targetMatchExact = targets != "*"; 
3190
					
3191
				// if they requested 'either' then just match scope. otherwise if they requested 'source' (not as a wildcard) then we have to match only endpoints that have isSource set to to true, and the same thing with isTarget.  
3192
				if ( either || source  || target ) {
3193
					inner:
3194
					for (var i = 0, ii = endpointsByElement[el].length; i < ii; i++) {
3195
						var _ep = endpointsByElement[el][i];
3196
						if (filterList(scopes, _ep.scope, true)) {
3197
						
3198
							var noMatchSource = (sourceMatchExact && sources.length > 0 && !_ep.isSource),
3199
								noMatchTarget = (targetMatchExact && targets.length > 0 && !_ep.isTarget);
3200
						
3201
							if (noMatchSource || noMatchTarget)								  
3202
								  continue inner; 
3203
							 							
3204
							ep.push(_ep);		
3205
						}
3206
					}
3207
				}					
3208
			}
3209
			
3210
			return _makeEndpointSelectHandler(ep);
3211
		};
3212
3213
		// get all connections managed by the instance of jsplumb.
3214
		this.getAllConnections = function() { return connections; };
3215
		this.getDefaultScope = function() { return DEFAULT_SCOPE; };
3216
		// get an endpoint by uuid.
3217
		this.getEndpoint = _getEndpoint;				
3218
		// get endpoints for some element.
3219
		this.getEndpoints = function(el) { return endpointsByElement[_info(el).id]; };		
3220
		// gets the default endpoint type. used when subclassing. see wiki.
3221
		this.getDefaultEndpointType = function() { return jsPlumb.Endpoint; };		
3222
		// gets the default connection type. used when subclassing.  see wiki.
3223
		this.getDefaultConnectionType = function() { return jsPlumb.Connection; };
3224
		/*
3225
		 * Gets an element's id, creating one if necessary. really only exposed
3226
		 * for the lib-specific functionality to access; would be better to pass
3227
		 * the current instance into the lib-specific code (even though this is
3228
		 * a static call. i just don't want to expose it to the public API).
3229
		 */
3230
		this.getId = _getId;
3231
		this.getOffset = function(id) { 
3232
			var o = offsets[id]; 
3233
			return _updateOffset({elId:id});
3234
		};
3235
3236
		this.getSelector = function() {
3237
			return jsPlumb.CurrentLibrary.getSelector.apply(null, arguments);
3238
		};
3239
		
3240
		// get the size of the element with the given id, perhaps from cache.
3241
		this.getSize = function(id) { 
3242
			var s = sizes[id]; 
3243
			if (!s) _updateOffset({elId:id});
3244
			return sizes[id];
3245
		};		
3246
		
3247
		this.appendElement = _appendElement;
3248
		
3249
		var _hoverSuspended = false;
3250
		this.isHoverSuspended = function() { return _hoverSuspended; };
3251
		this.setHoverSuspended = function(s) { _hoverSuspended = s; };
3252
3253
		var _isAvailable = function(m) {
3254
			return function() {
3255
				return jsPlumbAdapter.isRenderModeAvailable(m);
3256
			};
3257
		};
3258
		this.isCanvasAvailable = _isAvailable("canvas");
3259
		this.isSVGAvailable = _isAvailable("svg");
3260
		this.isVMLAvailable = _isAvailable("vml");
3261
3262
		// set an element's connections to be hidden
3263
		this.hide = function(el, changeEndpoints) {
3264
			_setVisible(el, "none", changeEndpoints);
3265
			return _currentInstance;
3266
		};
3267
		
3268
		// exposed for other objects to use to get a unique id.
3269
		this.idstamp = _idstamp;
3270
3271
		this.connectorsInitialized = false;
3272
		var connectorTypes = [], rendererTypes = ["canvas", "svg", "vml"];
3273
		this.registerConnectorType = function(connector, name) {
3274
			connectorTypes.push([connector, name]);
3275
		};
3276
		
3277
		/**
3278
		 * callback from the current library to tell us to prepare ourselves (attach
3279
		 * mouse listeners etc; can't do that until the library has provided a bind method)		 
3280
		 */
3281
		this.init = function() {
3282
			var _oneType = function(renderer, name, fn) {
3283
				jsPlumb.Connectors[renderer][name] = function() {
3284
					fn.apply(this, arguments);
3285
					jsPlumb.ConnectorRenderers[renderer].apply(this, arguments);		
3286
				};
3287
				jsPlumbUtil.extend(jsPlumb.Connectors[renderer][name], [ fn, jsPlumb.ConnectorRenderers[renderer]]);
3288
			};
3289
3290
			if (!jsPlumb.connectorsInitialized) {
3291
				for (var i = 0; i < connectorTypes.length; i++) {
3292
					for (var j = 0; j < rendererTypes.length; j++) {
3293
						_oneType(rendererTypes[j], connectorTypes[i][1], connectorTypes[i][0]);												
3294
					}
3295
3296
				}
3297
				jsPlumb.connectorsInitialized = true;
3298
			}
3299
			
3300
			if (!initialized) {                
3301
                _currentInstance.anchorManager = new jsPlumb.AnchorManager({jsPlumbInstance:_currentInstance});                
3302
				_currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode);  // calling the method forces the capability logic to be run.														
3303
				initialized = true;
3304
				_currentInstance.fire("ready", _currentInstance);
3305
			}
3306
		}.bind(this);		
3307
		
3308
		this.log = log;
3309
		this.jsPlumbUIComponent = jsPlumbUIComponent;		
3310
3311
		/*
3312
		 * Creates an anchor with the given params.
3313
		 * 
3314
		 * 
3315
		 * Returns: The newly created Anchor.
3316
		 * Throws: an error if a named anchor was not found.
3317
		 */
3318
		this.makeAnchor = function() {
3319
			var pp, _a = function(t, p) {
3320
				if (jsPlumb.Anchors[t]) return new jsPlumb.Anchors[t](p);
3321
				if (!_currentInstance.Defaults.DoNotThrowErrors)
3322
					throw { msg:"jsPlumb: unknown anchor type '" + t + "'" };
3323
			};
3324
			if (arguments.length === 0) return null;
3325
			var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null;			
3326
			// if it appears to be an anchor already...
3327
			if (specimen.compute && specimen.getOrientation) return specimen;  //TODO hazy here about whether it should be added or is already added somehow.
3328
			// is it the name of an anchor type?
3329
			else if (typeof specimen == "string") {
3330
				newAnchor = _a(arguments[0], {elementId:elementId, jsPlumbInstance:_currentInstance});
3331
			}
3332
			// is it an array? it will be one of:
3333
			// 		an array of [spec, params] - this defines a single anchor, which may be dynamic, but has parameters.
3334
			//		an array of arrays - this defines some dynamic anchors
3335
			//		an array of numbers - this defines a single anchor.				
3336
			else if (_ju.isArray(specimen)) {
3337
				if (_ju.isArray(specimen[0]) || _ju.isString(specimen[0])) {
3338
					// if [spec, params] format
3339
					if (specimen.length == 2 && _ju.isObject(specimen[1])) {
3340
						// if first arg is a string, its a named anchor with params
3341
						if (_ju.isString(specimen[0])) {
3342
							pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]);
3343
							newAnchor = _a(specimen[0], pp);
3344
						}
3345
						// otherwise first arg is array, second is params. we treat as a dynamic anchor, which is fine
3346
						// even if the first arg has only one entry. you could argue all anchors should be implicitly dynamic in fact.
3347
						else {
3348
							pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance, anchors:specimen[0]}, specimen[1]);
3349
							newAnchor = new jsPlumb.DynamicAnchor(pp);
3350
						}
3351
					}
3352
					else
3353
						newAnchor = new jsPlumb.DynamicAnchor({anchors:specimen, selector:null, elementId:elementId, jsPlumbInstance:jsPlumbInstance});
3354
3355
				}
3356
				else {
3357
					var anchorParams = {
3358
						x:specimen[0], y:specimen[1],
3359
						orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0],
3360
						offsets : (specimen.length >= 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ],
3361
						elementId:elementId,
3362
                        jsPlumbInstance:jsPlumbInstance,
3363
                        cssClass:specimen.length == 7 ? specimen[6] : null
3364
					};						
3365
					newAnchor = new jsPlumb.Anchor(anchorParams);
3366
					newAnchor.clone = function() { return new jsPlumb.Anchor(anchorParams); };						 					
3367
				}
3368
			}
3369
			
3370
			if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp();
3371
			return newAnchor;
3372
		};
3373
3374
		/**
3375
		 * makes a list of anchors from the given list of types or coords, eg
3376
		 * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ]
3377
		 */
3378
		this.makeAnchors = function(types, elementId, jsPlumbInstance) {
3379
			var r = [];
3380
			for ( var i = 0, ii = types.length; i < ii; i++) {
3381
				if (typeof types[i] == "string")
3382
					r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance}));
3383
				else if (_ju.isArray(types[i]))
3384
					r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance));
3385
			}
3386
			return r;
3387
		};
3388
3389
		/**
3390
		 * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor
3391
		 * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will
3392
		 * not need to provide this - i think). 
3393
		 */
3394
		this.makeDynamicAnchor = function(anchors, anchorSelector) {
3395
			return new jsPlumb.DynamicAnchor({anchors:anchors, selector:anchorSelector, elementId:null, jsPlumbInstance:_currentInstance});
3396
		};
3397
		
3398
// --------------------- makeSource/makeTarget ---------------------------------------------- 
3399
		
3400
		var _targetEndpointDefinitions = {},
3401
			_targetEndpoints = {},
3402
			_targetEndpointsUnique = {},
3403
			_targetMaxConnections = {},
3404
			_setEndpointPaintStylesAndAnchor = function(ep, epIndex) {
3405
				ep.paintStyle = ep.paintStyle ||
3406
				 				_currentInstance.Defaults.EndpointStyles[epIndex] ||
3407
	                            _currentInstance.Defaults.EndpointStyle ||
3408
	                            jsPlumb.Defaults.EndpointStyles[epIndex] ||
3409
	                            jsPlumb.Defaults.EndpointStyle;
3410
				ep.hoverPaintStyle = ep.hoverPaintStyle ||
3411
	                           _currentInstance.Defaults.EndpointHoverStyles[epIndex] ||
3412
	                           _currentInstance.Defaults.EndpointHoverStyle ||
3413
	                           jsPlumb.Defaults.EndpointHoverStyles[epIndex] ||
3414
	                           jsPlumb.Defaults.EndpointHoverStyle;                            
3415
3416
				ep.anchor = ep.anchor ||
3417
	                      	_currentInstance.Defaults.Anchors[epIndex] ||
3418
	                      	_currentInstance.Defaults.Anchor ||
3419
	                      	jsPlumb.Defaults.Anchors[epIndex] ||
3420
	                      	jsPlumb.Defaults.Anchor;                           
3421
					
3422
				ep.endpoint = ep.endpoint ||
3423
							  _currentInstance.Defaults.Endpoints[epIndex] ||
3424
							  _currentInstance.Defaults.Endpoint ||
3425
							  jsPlumb.Defaults.Endpoints[epIndex] ||
3426
							  jsPlumb.Defaults.Endpoint;
3427
			},
3428
			// TODO put all the source stuff inside one parent, keyed by id.
3429
			_sourceEndpointDefinitions = {},
3430
			_sourceEndpoints = {},
3431
			_sourceEndpointsUnique = {},
3432
			_sourcesEnabled = {},
3433
			_sourceTriggers = {},
3434
			_sourceMaxConnections = {},
3435
			_targetsEnabled = {},
3436
			selectorFilter = function(evt, _el, selector) {	            
3437
                var t = evt.target || evt.srcElement, ok = false, 
3438
                    sel = _currentInstance.getSelector(_el, selector);
3439
                for (var j = 0; j < sel.length; j++) {
3440
                    if (sel[j] == t) {
3441
                        ok = true;
3442
                        break;
3443
                    }
3444
                }
3445
                return ok;	            
3446
	        };
3447
3448
		// see API docs
3449
		this.makeTarget = function(el, params, referenceParams) {						
3450
			
3451
			// put jsplumb ref into params without altering the params passed in
3452
			var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams);
3453
			jsPlumb.extend(p, params);
3454
3455
			// calculate appropriate paint styles and anchor from the params given			
3456
			_setEndpointPaintStylesAndAnchor(p, 1);                               
3457
3458
			var jpcl = jsPlumb.CurrentLibrary,
3459
			    targetScope = p.scope || _currentInstance.Defaults.Scope,
3460
			    deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false),
3461
			    maxConnections = p.maxConnections || -1,
3462
				onMaxConnections = p.onMaxConnections,
3463
3464
				_doOne = function(el) {
3465
					
3466
					// get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
3467
					// and use the endpoint definition if found.
3468
					// decode the info for this element (id and element)
3469
					var elInfo = _info(el), 
3470
						elid = elInfo.id,
3471
						proxyComponent = new jsPlumbUIComponent(p),
3472
						dropOptions = jsPlumb.extend({}, p.dropOptions || {});
3473
3474
					// store the definitions keyed against the element id.
3475
					_targetEndpointDefinitions[elid] = p;
3476
					_targetEndpointsUnique[elid] = p.uniqueEndpoint;
3477
					_targetMaxConnections[elid] = maxConnections;
3478
					_targetsEnabled[elid] = true;				
3479
3480
					var _drop = function() {
3481
						_currentInstance.currentlyDragging = false;
3482
						var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments),
3483
							targetCount = _currentInstance.select({target:elid}).length,
3484
							draggable = _gel(jpcl.getDragObject(arguments)),
3485
							id = _currentInstance.getAttribute(draggable, "dragId"),										
3486
							scope = _currentInstance.getAttribute(draggable, "originalScope"),
3487
							jpc = floatingConnections[id],
3488
							idx = jpc.endpoints[0].isFloating() ? 0 : 1,
3489
							// this is not necessarily correct. if the source is being dragged,
3490
							// then the source endpoint is actually the currently suspended endpoint.
3491
							source = jpc.endpoints[0],
3492
							_endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {};					
3493
							
3494
						if (!_targetsEnabled[elid] || _targetMaxConnections[elid] > 0 && targetCount >= _targetMaxConnections[elid]){
3495
							if (onMaxConnections) {
3496
								// TODO here we still have the id of the floating element, not the
3497
								// actual target.
3498
								onMaxConnections({
3499
									element:elInfo.el,
3500
									connection:jpc
3501
								}, originalEvent);
3502
							}
3503
							return false;
3504
						}
3505
3506
						// unlock the source anchor to allow it to refresh its position if necessary
3507
						source.anchor.locked = false;					
3508
											
3509
						// restore the original scope if necessary (issue 57)
3510
						if (scope) jpcl.setDragScope(draggable, scope);		
3511
3512
						// if no suspendedEndpoint and not pending, it is likely there was a drop on two 
3513
						// elements that are on top of each other. abort.
3514
						if (jpc.suspendedEndpoint == null && !jpc.pending)
3515
							return false;		
3516
						
3517
						// check if drop is allowed here.					
3518
						// if the source is being dragged then in fact
3519
						// the source and target ids to pass into the drop interceptor are
3520
						// source - elid
3521
						// target - jpc's targetId
3522
						// 
3523
						// otherwise the ids are
3524
						// source - jpc.sourceId
3525
						// target - elid
3526
						//
3527
						var _continue = proxyComponent.isDropAllowed(idx === 0 ? elid : jpc.sourceId, idx === 0 ? jpc.targetId : elid, jpc.scope, jpc, null);							
3528
3529
						// reinstate any suspended endpoint; this just puts the connection back into
3530
						// a state in which it will report sensible values if someone asks it about
3531
						// its target.  we're going to throw this connection away shortly so it doesnt matter
3532
						// if we manipulate it a bit.
3533
						if (jpc.suspendedEndpoint) {
3534
							jpc[idx ? "targetId" : "sourceId"] = jpc.suspendedEndpoint.elementId;
3535
							jpc[idx ? "target" : "source"] = jpc.suspendedEndpoint.element;
3536
							jpc.endpoints[idx] = jpc.suspendedEndpoint;
3537
						}																										
3538
						
3539
						if (_continue) {
3540
																	
3541
							// make a new Endpoint for the target, or get it from the cache if uniqueEndpoint
3542
                            // is set.
3543
							var _el = jpcl.getElementObject(elInfo.el),
3544
								newEndpoint = _targetEndpoints[elid];
3545
3546
                            // if no cached endpoint, or there was one but it has been cleaned up
3547
                            // (ie. detached), then create a new one.
3548
                            if (newEndpoint == null || newEndpoint._jsPlumb == null)
3549
                                newEndpoint = _currentInstance.addEndpoint(_el, p);
3550
3551
							if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint;  // may of course just store what it just pulled out. that's ok.
3552
							// TODO test options to makeTarget to see if we should do this?
3553
							newEndpoint._doNotDeleteOnDetach = false; // reset.
3554
							newEndpoint._deleteOnDetach = true;
3555
																	
3556
							// if the anchor has a 'positionFinder' set, then delegate to that function to find
3557
							// out where to locate the anchor.
3558
							if (newEndpoint.anchor.positionFinder != null) {
3559
								var dropPosition = jpcl.getUIPosition(arguments, _currentInstance.getZoom()),
3560
								elPosition = _getOffset(_el, _currentInstance),
3561
								elSize = _getSize(_el),
3562
								ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams);
3563
								newEndpoint.anchor.x = ap[0];
3564
								newEndpoint.anchor.y = ap[1];
3565
								// now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to
3566
								// support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation 
3567
								// be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be
3568
								// specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which
3569
								// the target is furthest away from the source.
3570
							}
3571
							
3572
							// change the target endpoint and target element information. really this should be 
3573
							// done on a method on connection
3574
							jpc[idx ? "target" : "source"] = newEndpoint.element;
3575
							jpc[idx ? "targetId" : "sourceId"] = newEndpoint.elementId;
3576
							jpc.endpoints[idx].detachFromConnection(jpc);
3577
							if (jpc.endpoints[idx]._deleteOnDetach)
3578
								jpc.endpoints[idx].deleteAfterDragStop = true; // tell this endpoint to delet itself after drag stop.
3579
							// set new endpoint, and configure the settings for endpoints to delete on detach
3580
							newEndpoint.addConnection(jpc);
3581
							jpc.endpoints[idx] = newEndpoint;
3582
							jpc.deleteEndpointsOnDetach = deleteEndpointsOnDetach;						
3583
3584
							// inform the anchor manager to update its target endpoint for this connection.
3585
							// TODO refactor to make this a single method.
3586
							if (idx == 1)
3587
								_currentInstance.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.suspendedElementId, jpc.targetId, jpc);
3588
							else
3589
								_currentInstance.anchorManager.sourceChanged(jpc.suspendedEndpoint.elementId, jpc.sourceId, jpc);
3590
3591
							_finaliseConnection(jpc, null, originalEvent);
3592
							jpc.pending = false;
3593
3594
						}				
3595
						// if not allowed to drop...
3596
						else {
3597
							// TODO this code is identical (pretty much) to what happens when a connection
3598
							// dragged from a normal endpoint is in this situation. refactor.
3599
							// is this an existing connection, and will we reattach?
3600
							// TODO also this assumes the source needs to detach - is that always valid?
3601
							if (jpc.suspendedEndpoint) {							
3602
								if (jpc.isReattach()) {
3603
									jpc.setHover(false);
3604
									jpc.floatingAnchorIndex = null;
3605
									jpc.suspendedEndpoint.addConnection(jpc);
3606
									_currentInstance.repaint(source.elementId);
3607
								}
3608
								else
3609
									source.detach(jpc, false, true, true, originalEvent);  // otherwise, detach the connection and tell everyone about it.
3610
							}
3611
							
3612
						}														
3613
					};
3614
					
3615
					// wrap drop events as needed and initialise droppable
3616
					var dropEvent = jpcl.dragEvents.drop;
3617
					dropOptions.scope = dropOptions.scope || targetScope;
3618
					dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], _drop);				
3619
					jpcl.initDroppable(_gel(elInfo.el), dropOptions, true);
3620
				};
3621
			
3622
			// YUI collection fix
3623
			el = _convertYUICollection(el);			
3624
			// make an array if only given one element
3625
			var inputs = el.length && el.constructor != String ? el : [ el ];
3626
						
3627
			// register each one in the list.
3628
			for (var i = 0, ii = inputs.length; i < ii; i++) {							
3629
				_doOne(inputs[i]);
3630
			}
3631
3632
			return _currentInstance;
3633
		};
3634
3635
		// see api docs
3636
		this.unmakeTarget = function(el, doNotClearArrays) {
3637
			var info = _info(el);
3638
3639
			jsPlumb.CurrentLibrary.destroyDroppable(info.el);
3640
			// TODO this is not an exhaustive unmake of a target, since it does not remove the droppable stuff from
3641
			// the element.  the effect will be to prevent it from behaving as a target, but it's not completely purged.
3642
			if (!doNotClearArrays) {
3643
				delete _targetEndpointDefinitions[info.id];
3644
				delete _targetEndpointsUnique[info.id];
3645
				delete _targetMaxConnections[info.id];
3646
				delete _targetsEnabled[info.id];                
3647
			}
3648
3649
			return _currentInstance;
3650
		};						
3651
3652
	    // see api docs
3653
		this.makeSource = function(el, params, referenceParams) {
3654
			var p = jsPlumb.extend({}, referenceParams);
3655
			jsPlumb.extend(p, params);
3656
			_setEndpointPaintStylesAndAnchor(p, 0);   
3657
			var jpcl = jsPlumb.CurrentLibrary,
3658
				maxConnections = p.maxConnections || -1,
3659
				onMaxConnections = p.onMaxConnections,
3660
				_doOne = function(elInfo) {
3661
					// get the element's id and store the endpoint definition for it.  jsPlumb.connect calls will look for one of these,
3662
					// and use the endpoint definition if found.
3663
					var elid = elInfo.id,
3664
						_el = _gel(elInfo.el),
3665
						parentElement = function() {
3666
							return p.parent == null ? null : p.parent === "parent" ? elInfo.el.parentNode : _dom(p.parent);
3667
						},
3668
						idToRegisterAgainst = p.parent != null ? _currentInstance.getId(parentElement()) : elid;
3669
					
3670
					_sourceEndpointDefinitions[idToRegisterAgainst] = p;
3671
					_sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint;
3672
					_sourcesEnabled[idToRegisterAgainst] = true;
3673
3674
					var stopEvent = jpcl.dragEvents.stop,
3675
						dragEvent = jpcl.dragEvents.drag,
3676
						dragOptions = jsPlumb.extend({ }, p.dragOptions || {}),
3677
						existingDrag = dragOptions.drag,
3678
						existingStop = dragOptions.stop,
3679
						ep = null,
3680
						endpointAddedButNoDragYet = false;
3681
				
3682
					_sourceMaxConnections[idToRegisterAgainst] = maxConnections;	
3683
3684
					// set scope if its not set in dragOptions but was passed in in params
3685
					dragOptions.scope = dragOptions.scope || p.scope;
3686
3687
					dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], function() {
3688
						if (existingDrag) existingDrag.apply(this, arguments);
3689
						endpointAddedButNoDragYet = false;
3690
					});
3691
					
3692
					dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent], function() { 
3693
3694
						if (existingStop) existingStop.apply(this, arguments);								
3695
	                    _currentInstance.currentlyDragging = false;						
3696
						if (ep._jsPlumb != null) { // if not cleaned up...
3697
3698
							jpcl.unbind(ep.canvas, "mousedown"); 
3699
									
3700
							// reset the anchor to the anchor that was initially provided. the one we were using to drag
3701
							// the connection was just a placeholder that was located at the place the user pressed the
3702
							// mouse button to initiate the drag.
3703
							var anchorDef = p.anchor || _currentInstance.Defaults.Anchor,
3704
								oldAnchor = ep.anchor,
3705
								oldConnection = ep.connections[0],
3706
								newAnchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance),
3707
								_el = ep.element;
3708
3709
							// if the anchor has a 'positionFinder' set, then delegate to that function to find
3710
							// out where to locate the anchor. issue 117.
3711
							if (newAnchor.positionFinder != null) {
3712
								var elPosition = _getOffset(_el, _currentInstance),
3713
									elSize = _getSize(_el),
3714
									dropPosition = { left:elPosition.left + (oldAnchor.x * elSize[0]), top:elPosition.top + (oldAnchor.y * elSize[1]) },
3715
									ap = newAnchor.positionFinder(dropPosition, elPosition, elSize, newAnchor.constructorParams);
3716
3717
								newAnchor.x = ap[0];
3718
								newAnchor.y = ap[1];
3719
							}
3720
3721
							ep.setAnchor(newAnchor, true);																							
3722
							
3723
							if (p.parent) {						
3724
								var parent = parentElement();
3725
								if (parent) {	
3726
									var potentialParent = p.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container;
3727
									ep.setElement(parent, potentialParent);
3728
								}
3729
							}						
3730
							
3731
							ep.repaint();			
3732
							_currentInstance.repaint(ep.elementId);																		
3733
							_currentInstance.repaint(oldConnection.targetId);
3734
						}				
3735
					});
3736
					// when the user presses the mouse, add an Endpoint, if we are enabled.
3737
					var mouseDownListener = function(e) {
3738
3739
						// if disabled, return.
3740
						if (!_sourcesEnabled[idToRegisterAgainst]) return;
3741
	                    
3742
	                    // if a filter was given, run it, and return if it says no.
3743
						if (p.filter) {
3744
							var evt = jpcl.getOriginalEvent(e),
3745
								r = jsPlumbUtil.isString(p.filter) ? selectorFilter(evt, _el, p.filter) : p.filter(evt, _el);
3746
							
3747
							if (r === false) return;
3748
						}
3749
						
3750
						// if maxConnections reached
3751
						var sourceCount = _currentInstance.select({source:idToRegisterAgainst}).length;
3752
						if (_sourceMaxConnections[idToRegisterAgainst] >= 0 && sourceCount >= _sourceMaxConnections[idToRegisterAgainst]) {
3753
							if (onMaxConnections) {
3754
								onMaxConnections({
3755
									element:_el,
3756
									maxConnections:maxConnections
3757
								}, e);
3758
							}
3759
							return false;
3760
						}					
3761
3762
						// make sure we have the latest offset for this div 
3763
						var myOffsetInfo = _updateOffset({elId:elid}).o,
3764
							z = _currentInstance.getZoom(),		
3765
							x = ( ((e.pageX || e.page.x) / z) - myOffsetInfo.left) / myOffsetInfo.width, 
3766
						    y = ( ((e.pageY || e.page.y) / z) - myOffsetInfo.top) / myOffsetInfo.height,
3767
						    parentX = x, 
3768
						    parentY = y;					
3769
								
3770
						// if there is a parent, the endpoint will actually be added to it now, rather than the div
3771
						// that was the source.  in that case, we have to adjust the anchor position so it refers to
3772
						// the parent.
3773
						if (p.parent) {
3774
							var pEl = parentElement(), pId = _getId(pEl);
3775
							myOffsetInfo = _updateOffset({elId:pId}).o;
3776
							parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width; 
3777
						    parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height;
3778
						}											
3779
						
3780
						// we need to override the anchor in here, and force 'isSource', but we don't want to mess with
3781
						// the params passed in, because after a connection is established we're going to reset the endpoint
3782
						// to have the anchor we were given.
3783
						var tempEndpointParams = {};
3784
						jsPlumb.extend(tempEndpointParams, p);
3785
						tempEndpointParams.isSource = true;
3786
						tempEndpointParams.anchor = [x,y,0,0];
3787
						tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ];
3788
						tempEndpointParams.dragOptions = dragOptions;
3789
						// if a parent was given we need to turn that into a "container" argument.  this is, by default,
3790
						// the parent of the element we will move to, so parent of p.parent in this case.  however, if
3791
						// the user has specified a 'container' on the endpoint definition or on 
3792
						// the defaults, we should use that.
3793
						if (p.parent) {
3794
							var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container;
3795
							if (potentialParent)
3796
								tempEndpointParams.container = potentialParent;
3797
							else
3798
								tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(parentElement());
3799
						}
3800
						
3801
						ep = _currentInstance.addEndpoint(elid, tempEndpointParams);
3802
3803
						endpointAddedButNoDragYet = true;
3804
						// we set this to prevent connections from firing attach events before this function has had a chance
3805
						// to move the endpoint.
3806
						ep.endpointWillMoveAfterConnection = p.parent != null;
3807
						ep.endpointWillMoveTo = p.parent ? parentElement() : null;
3808
3809
						// TODO test options to makeSource to see if we should do this?
3810
						ep._doNotDeleteOnDetach = false; // reset.
3811
						ep._deleteOnDetach = true;
3812
3813
	                    var _delTempEndpoint = function() {
3814
							// this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools
3815
							// it is fired even if dragging has occurred, in which case we would blow away a perfectly
3816
							// legitimate endpoint, were it not for this check.  the flag is set after adding an
3817
							// endpoint and cleared in a drag listener we set in the dragOptions above.
3818
							if(endpointAddedButNoDragYet) {
3819
								 endpointAddedButNoDragYet = false;
3820
								_currentInstance.deleteEndpoint(ep);
3821
	                        }
3822
						};
3823
3824
						_currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint);
3825
	                    _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint);
3826
						
3827
						// and then trigger its mousedown event, which will kick off a drag, which will start dragging
3828
						// a new connection from this endpoint.
3829
						jpcl.trigger(ep.canvas, "mousedown", e);
3830
						
3831
					};
3832
	               
3833
	                // register this on jsPlumb so that it can be cleared by a reset.
3834
	                _currentInstance.registerListener(_el, "mousedown", mouseDownListener);
3835
	                _sourceTriggers[elid] = mouseDownListener;
3836
3837
	                // lastly, if a filter was provided, set it as a dragFilter on the element,
3838
	                // to prevent the element drag function from kicking in when we want to
3839
	                // drag a new connection
3840
	                if (p.filter && jsPlumbUtil.isString(p.filter)) {
3841
	                	jpcl.setDragFilter(_el, p.filter);
3842
	                }
3843
				};
3844
			
3845
			el = _convertYUICollection(el);			
3846
			
3847
			var inputs = el.length && el.constructor != String ? el : [ el ];
3848
						
3849
			for (var i = 0, ii = inputs.length; i < ii; i++) {			
3850
				_doOne(_info(inputs[i]));
3851
			}
3852
3853
			return _currentInstance;
3854
		};
3855
	
3856
		// see api docs		
3857
		this.unmakeSource = function(el, doNotClearArrays) {
3858
			var info = _info(el),
3859
				mouseDownListener = _sourceTriggers[info.id];
3860
			
3861
			if (mouseDownListener) 
3862
				_currentInstance.unregisterListener(info.el, "mousedown", mouseDownListener);
3863
3864
			if (!doNotClearArrays) {
3865
				delete _sourceEndpointDefinitions[info.id];
3866
				delete _sourceEndpointsUnique[info.id];
3867
				delete _sourcesEnabled[info.id];
3868
				delete _sourceTriggers[info.id];
3869
				delete _sourceMaxConnections[info.id];
3870
			}
3871
3872
			return _currentInstance;
3873
		};
3874
3875
		// see api docs
3876
		this.unmakeEverySource = function() {
3877
			for (var i in _sourcesEnabled)
3878
				_currentInstance.unmakeSource(i, true);
3879
3880
			_sourceEndpointDefinitions = {};
3881
			_sourceEndpointsUnique = {};
3882
			_sourcesEnabled = {};
3883
			_sourceTriggers = {};
3884
		};
3885
		
3886
		// see api docs
3887
		this.unmakeEveryTarget = function() {
3888
			for (var i in _targetsEnabled)
3889
				_currentInstance.unmakeTarget(i, true);
3890
			
3891
			_targetEndpointDefinitions = {};
3892
			_targetEndpointsUnique = {};
3893
			_targetMaxConnections = {};
3894
			_targetsEnabled = {};
3895
3896
			return _currentInstance;
3897
		};			
3898
3899
		// does the work of setting a source enabled or disabled.
3900
		var _setEnabled = function(type, el, state, toggle) {
3901
			var a = type == "source" ? _sourcesEnabled : _targetsEnabled;									
3902
			el = _convertYUICollection(el);
3903
3904
			if (_ju.isString(el)) a[el] = toggle ? !a[el] : state;
3905
			else if (el.length) {				
3906
				for (var i = 0, ii = el.length; i < ii; i++) {
3907
					var info = _info(el[i]);
3908
					a[info.id] = toggle ? !a[info.id] : state;
3909
				}
3910
			}	
3911
			return _currentInstance;
3912
		};
3913
3914
		this.toggleSourceEnabled = function(el) {
3915
			_setEnabled("source", el, null, true);	
3916
			return _currentInstance.isSourceEnabled(el);
3917
		};
3918
3919
		this.setSourceEnabled = function(el, state) { return _setEnabled("source", el, state); };
3920
		this.isSource = function(el) { return _sourcesEnabled[_info(el).id] != null; };		
3921
		this.isSourceEnabled = function(el) { return _sourcesEnabled[_info(el).id] === true; };
3922
3923
		this.toggleTargetEnabled = function(el) {
3924
			_setEnabled("target", el, null, true);	
3925
			return _currentInstance.isTargetEnabled(el);
3926
		};
3927
		
3928
		this.isTarget = function(el) { return _targetsEnabled[_info(el).id] != null; };		
3929
		this.isTargetEnabled = function(el) { return _targetsEnabled[_info(el).id] === true; };
3930
		this.setTargetEnabled = function(el, state) { return _setEnabled("target", el, state); };
3931
3932
// --------------------- end makeSource/makeTarget ---------------------------------------------- 				
3933
				
3934
		this.ready = function(fn) {
3935
			_currentInstance.bind("ready", fn);
3936
		};
3937
3938
		// repaint some element's endpoints and connections
3939
		this.repaint = function(el, ui, timestamp) {
3940
			// support both lists...
3941
			if (typeof el == 'object' && el.length)
3942
				for ( var i = 0, ii = el.length; i < ii; i++) {			
3943
					_draw(el[i], ui, timestamp);
3944
				}
3945
			else // ...and single strings.								
3946
				_draw(el, ui, timestamp);
3947
				
3948
			return _currentInstance;
3949
		};
3950
3951
		// repaint every endpoint and connection.
3952
		this.repaintEverything = function(clearEdits) {	
3953
			// TODO this timestamp causes continuous anchors to not repaint properly.
3954
			// fix this. do not just take out the timestamp. it runs a lot faster with 
3955
			// the timestamp included.
3956
			//var timestamp = null;
3957
			var timestamp = _timestamp();
3958
			for ( var elId in endpointsByElement) {
3959
				_draw(elId, null, timestamp, clearEdits);				
3960
			}
3961
			return _currentInstance;
3962
		};
3963
3964
		
3965
		this.removeAllEndpoints = function(el, recurse) {
3966
            var _one = function(_el) {                            	
3967
                var info = _info(_el),
3968
                    ebe = endpointsByElement[info.id],
3969
                    i, ii;
3970
3971
                if (ebe) {
3972
                    for ( i = 0, ii = ebe.length; i < ii; i++) 
3973
                        _currentInstance.deleteEndpoint(ebe[i]);
3974
                }
3975
                delete endpointsByElement[info.id];
3976
                
3977
                if (recurse) {
3978
                    if (info.el && info.el.nodeType != 3 && info.el.nodeType != 8 ) {
3979
                        for ( i = 0, ii = info.el.childNodes.length; i < ii; i++) {
3980
                            _one(info.el.childNodes[i]);
3981
                        }
3982
                    }
3983
                }
3984
                
3985
            };
3986
            _one(el);
3987
			return _currentInstance;
3988
		};
3989
                    
3990
        /**
3991
        * Remove the given element, including cleaning up all endpoints registered for it.
3992
        * This is exposed in the public API but also used internally by jsPlumb when removing the
3993
        * element associated with a connection drag.
3994
        */
3995
        this.remove = function(el, doNotRepaint) {
3996
        	var info = _info(el);        	
3997
            _currentInstance.doWhileSuspended(function() {
3998
            	_currentInstance.removeAllEndpoints(info.id, true);
3999
            	_currentInstance.dragManager.elementRemoved(info.id);
4000
            	delete floatingConnections[info.id];     
4001
            	_currentInstance.anchorManager.clearFor(info.id);						
4002
            	_currentInstance.anchorManager.removeFloatingConnection(info.id);
4003
            }, doNotRepaint === false);
4004
            if(info.el) jsPlumb.CurrentLibrary.removeElement(info.el);
4005
        };
4006
4007
		var _registeredListeners = {},
4008
			_unbindRegisteredListeners = function() {
4009
				for (var i in _registeredListeners) {
4010
					for (var j = 0, jj = _registeredListeners[i].length; j < jj; j++) {
4011
						var info = _registeredListeners[i][j];
4012
						jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener);
4013
					}
4014
				}
4015
				_registeredListeners = {};
4016
			};
4017
4018
        // internal register listener method.  gives us a hook to clean things up
4019
        // with if the user calls jsPlumb.reset.
4020
        this.registerListener = function(el, type, listener) {
4021
            jsPlumb.CurrentLibrary.bind(el, type, listener);
4022
            jsPlumbUtil.addToList(_registeredListeners, type, {el:el, event:type, listener:listener});
4023
        };
4024
4025
        this.unregisterListener = function(el, type, listener) {
4026
        	jsPlumb.CurrentLibrary.unbind(el, type, listener);
4027
        	jsPlumbUtil.removeWithFunction(_registeredListeners, function(rl) {
4028
        		return rl.type == type && rl.listener == listener;
4029
        	});
4030
        };
4031
		
4032
		this.reset = function() {			
4033
			_currentInstance.deleteEveryEndpoint();
4034
			_currentInstance.unbind();
4035
			_targetEndpointDefinitions = {};
4036
			_targetEndpoints = {};
4037
			_targetEndpointsUnique = {};
4038
			_targetMaxConnections = {};
4039
			_sourceEndpointDefinitions = {};
4040
			_sourceEndpoints = {};
4041
			_sourceEndpointsUnique = {};
4042
			_sourceMaxConnections = {};
4043
			connections.splice(0);
4044
			_unbindRegisteredListeners();
4045
			_currentInstance.anchorManager.reset();
4046
			if (!jsPlumbAdapter.headless)
4047
				_currentInstance.dragManager.reset();
4048
		};
4049
		
4050
4051
		this.setDefaultScope = function(scope) {
4052
			DEFAULT_SCOPE = scope;
4053
			return _currentInstance;
4054
		};
4055
4056
		// sets whether or not some element should be currently draggable.
4057
		this.setDraggable = _setDraggable;
4058
4059
		// sets the id of some element, changing whatever we need to to keep track.
4060
		this.setId = function(el, newId, doNotSetAttribute) {
4061
			// 
4062
			var id;
4063
4064
			if (jsPlumbUtil.isString(el)) {
4065
				id = el;				
4066
			}
4067
			else {
4068
				el = _dom(el);
4069
				id = _currentInstance.getId(el);
4070
			}
4071
4072
			var sConns = _currentInstance.getConnections({source:id, scope:'*'}, true),
4073
				tConns = _currentInstance.getConnections({target:id, scope:'*'}, true);
4074
4075
			newId = "" + newId;
4076
4077
			if (!doNotSetAttribute) {
4078
				el = _dom(id);
4079
				jsPlumbAdapter.setAttribute(el, "id", newId);
4080
			}
4081
			else
4082
				el = _dom(newId);
4083
4084
			endpointsByElement[newId] = endpointsByElement[id] || [];
4085
			for (var i = 0, ii = endpointsByElement[newId].length; i < ii; i++) {
4086
				endpointsByElement[newId][i].setElementId(newId);
4087
				endpointsByElement[newId][i].setReferenceElement(el);
4088
			}
4089
			delete endpointsByElement[id];
4090
4091
			_currentInstance.anchorManager.changeId(id, newId);
4092
			if (!jsPlumbAdapter.headless)		
4093
				_currentInstance.dragManager.changeId(id, newId);
4094
4095
			var _conns = function(list, epIdx, type) {
4096
				for (var i = 0, ii = list.length; i < ii; i++) {
4097
					list[i].endpoints[epIdx].setElementId(newId);
4098
					list[i].endpoints[epIdx].setReferenceElement(el);
4099
					list[i][type + "Id"] = newId;
4100
					list[i][type] = el;
4101
				}
4102
			};
4103
			_conns(sConns, 0, "source");
4104
			_conns(tConns, 1, "target");
4105
4106
			_currentInstance.repaint(newId);
4107
		};		
4108
4109
		this.setDebugLog = function(debugLog) {
4110
			log = debugLog;
4111
		};
4112
			          	
4113
		this.setSuspendDrawing = function(val, repaintAfterwards) {
4114
			var curVal = _suspendDrawing;
4115
		    _suspendDrawing = val;
4116
				if (val) _suspendedAt = new Date().getTime(); else _suspendedAt = null;
4117
		    if (repaintAfterwards) _currentInstance.repaintEverything();
4118
		    return curVal;
4119
		};
4120
        	
4121
        // returns whether or not drawing is currently suspended.		
4122
		this.isSuspendDrawing = function() {
4123
			return _suspendDrawing;
4124
		};
4125
            
4126
        // return timestamp for when drawing was suspended.
4127
        this.getSuspendedAt = function() { return _suspendedAt; };
4128
4129
        /**
4130
        * @doc function
4131
        * @name jsPlumb.class:doWhileSuspended
4132
        * @param {function} fn Function to run while suspended.
4133
        * @param {boolean} doNotRepaintAfterwards If true, jsPlumb won't run a full repaint. Otherwise it will.
4134
        * @description Suspends drawing, runs the given function, then re-enables drawing (and repaints, unless you tell it not to)
4135
        */
4136
        this.doWhileSuspended = function(fn, doNotRepaintAfterwards) {     
4137
        	var _wasSuspended = _currentInstance.isSuspendDrawing();        	
4138
        	if (!_wasSuspended)
4139
				_currentInstance.setSuspendDrawing(true);
4140
			try {
4141
				fn();
4142
			}
4143
			catch (e) {
4144
				_ju.log("Function run while suspended failed", e);
4145
			}			
4146
			if (!_wasSuspended)
4147
				_currentInstance.setSuspendDrawing(false, !doNotRepaintAfterwards);
4148
        };
4149
            
4150
        this.updateOffset = _updateOffset;
4151
        this.getOffset = function(elId) { return offsets[elId]; };
4152
        this.getSize = function(elId) { return sizes[elId]; };            
4153
        this.getCachedData = _getCachedData;
4154
        this.timestamp = _timestamp;
4155
		
4156
		
4157
		
4158
		/**
4159
		 * @doc function
4160
		 * @name jsPlumb.class:setRenderMode
4161
		 * @param {string} mode One of `jsPlumb.SVG, `jsPlumb.VML` or `jsPlumb.CANVAS`.
4162
		 * @description Sets render mode.  jsPlumb will fall back to VML if it determines that
4163
		 * what you asked for is not supported (and that VML is).  If you asked for VML but the browser does
4164
		 * not support it, jsPlumb uses SVG.
4165
		 * @return {string} The render mode that jsPlumb set, which of course may be different from that requested.
4166
		 */
4167
		this.setRenderMode = function(mode) {			
4168
			renderMode = jsPlumbAdapter.setRenderMode(mode);
4169
			var i, ii;
4170
			// only add this if the renderer is canvas; we dont want these listeners registered on te
4171
			// entire document otherwise.
4172
			if (renderMode == jsPlumb.CANVAS) {
4173
				var bindOne = function(event) {
4174
	                jsPlumb.CurrentLibrary.bind(document, event, function(e) {
4175
	                    if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) {
4176
	                        // try connections first
4177
	                        for (i = 0, ii = connections.length; i < ii; i++ ) {
4178
                                var t = connections[i].getConnector()[event](e);
4179
                                if (t) return;	
4180
                            }
4181
	                        for (var el in endpointsByElement) {
4182
	                            var ee = endpointsByElement[el];
4183
	                            for ( i = 0, ii = ee.length; i < ii; i++ ) {
4184
	                                if (ee[i].endpoint[event] && ee[i].endpoint[event](e)) return;
4185
	                            }
4186
	                        }
4187
	                    }
4188
	                });					
4189
				};
4190
				bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu");				
4191
			}
4192
4193
			return renderMode;
4194
		};
4195
		
4196
		/**
4197
		 * @doc function
4198
		 * @name jsPlumb.class:getRenderMode
4199
		 * @description Gets the current render mode for this instance of jsPlumb.
4200
		 * @return {string} The current render mode - "canvas", "svg" or "vml".
4201
		 */
4202
		this.getRenderMode = function() { return renderMode; };
4203
		
4204
		this.show = function(el, changeEndpoints) {
4205
			_setVisible(el, "block", changeEndpoints);
4206
			return _currentInstance;
4207
		};		
4208
4209
		/**
4210
		 * gets some test hooks. nothing writable.
4211
		 */
4212
		this.getTestHarness = function() {
4213
			return {
4214
				endpointsByElement : endpointsByElement,  
4215
				endpointCount : function(elId) {
4216
					var e = endpointsByElement[elId];
4217
					return e ? e.length : 0;
4218
				},
4219
				connectionCount : function(scope) {
4220
					scope = scope || DEFAULT_SCOPE;
4221
					var c = _currentInstance.getConnections({scope:scope});
4222
					return c ? c.length : 0;
4223
				},
4224
				getId : _getId,
4225
				makeAnchor:self.makeAnchor,
4226
				makeDynamicAnchor:self.makeDynamicAnchor
4227
			};
4228
		};
4229
		
4230
		
4231
		// TODO: update this method to return the current state.
4232
		this.toggleVisible = _toggleVisible;
4233
		this.toggleDraggable = _toggleDraggable;						
4234
		this.addListener = this.bind;
4235
		
4236
        /*
4237
            helper method to take an xy location and adjust it for the parent's offset and scroll.
4238
        */
4239
		this.adjustForParentOffsetAndScroll = function(xy, el) {
4240
4241
			var offsetParent = null, result = xy;
4242
			if (el.tagName.toLowerCase() === "svg" && el.parentNode) {
4243
				offsetParent = el.parentNode;
4244
			}
4245
			else if (el.offsetParent) {
4246
				offsetParent = el.offsetParent;					
4247
			}
4248
			if (offsetParent != null) {
4249
				var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent, _currentInstance),
4250
					so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop};					
4251
4252
				// i thought it might be cool to do this:
4253
				//	lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft;
4254
				//	lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop;					
4255
				// but i think it ignores margins.  my reasoning was that it's quicker to not hand off to some underlying					
4256
				// library.
4257
				
4258
				result[0] = xy[0] - po.left + so.left;
4259
				result[1] = xy[1] - po.top + so.top;
4260
			}
4261
		
4262
			return result;
4263
			
4264
		};
4265
4266
		if (!jsPlumbAdapter.headless) {
4267
			_currentInstance.dragManager = jsPlumbAdapter.getDragManager(_currentInstance);
4268
			_currentInstance.recalculateOffsets = _currentInstance.dragManager.updateOffsets;
4269
	    }	    
4270
				    
4271
    };
4272
4273
    jsPlumbUtil.extend(jsPlumbInstance, jsPlumbUtil.EventGenerator, {
4274
    	setAttribute : function(el, a, v) {
4275
    		jsPlumbAdapter.setAttribute(el, a, v);
4276
    	},
4277
    	getAttribute : function(el, a) {
4278
    		return jsPlumbAdapter.getAttribute(jsPlumb.CurrentLibrary.getDOMElement(el), a);
4279
    	},    	
4280
    	registerConnectionType : function(id, type) {
4281
    		this._connectionTypes[id] = jsPlumb.extend({}, type);
4282
    	},    	
4283
    	registerConnectionTypes : function(types) {
4284
    		for (var i in types)
4285
    			this._connectionTypes[i] = jsPlumb.extend({}, types[i]);
4286
    	},    	    	
4287
    	registerEndpointType : function(id, type) {
4288
    		this._endpointTypes[id] = jsPlumb.extend({}, type);
4289
    	},    	
4290
    	registerEndpointTypes : function(types) {
4291
    		for (var i in types)
4292
    			this._endpointTypes[i] = jsPlumb.extend({}, types[i]);
4293
    	},    	
4294
    	getType : function(id, typeDescriptor) {
4295
    		return typeDescriptor ===  "connection" ? this._connectionTypes[id] : this._endpointTypes[id];
4296
    	},
4297
    	setIdChanged : function(oldId, newId) {
4298
    		this.setId(oldId, newId, true);
4299
    	},
4300
    	// set parent: change the parent for some node and update all the registrations we need to.
4301
    	setParent : function(el, newParent) {
4302
    		var jpcl = jsPlumb.CurrentLibrary,
4303
    			_el = jpcl.getElementObject(el),
4304
    			_dom = jpcl.getDOMElement(_el),
4305
    			_id = this.getId(_dom),
4306
    			_pel = jpcl.getElementObject(newParent),
4307
    			_pdom = jpcl.getDOMElement(_pel),
4308
    			_pid = this.getId(_pdom);
4309
4310
    		_dom.parentNode.removeChild(_dom);
4311
    		_pdom.appendChild(_dom);
4312
    		this.dragManager.setParent(_el, _id, _pel, _pid);
4313
    	}
4314
    });
4315
4316
// --------------------- static instance + AMD registration -------------------------------------------	
4317
	
4318
// create static instance and assign to window if window exists.	
4319
	var jsPlumb = new jsPlumbInstance();
4320
	// register on window if defined (lets us run on server)
4321
	if (typeof window != 'undefined') window.jsPlumb = jsPlumb;	
4322
	// add 'getInstance' method to static instance
4323
	/**
4324
	* @name jsPlumb.getInstance
4325
	* @param {object} [_defaults] Optional default settings for the new instance.
4326
	* @desc Gets a new instance of jsPlumb.
4327
	*/
4328
	jsPlumb.getInstance = function(_defaults) {
4329
		var j = new jsPlumbInstance(_defaults);
4330
		j.init();
4331
		return j;
4332
	};
4333
// maybe register static instance as an AMD module, and getInstance method too.
4334
	if ( typeof define === "function") {
4335
		define( "jsplumb", [], function () { return jsPlumb; } );
4336
		define( "jsplumbinstance", [], function () { return jsPlumb.getInstance(); } );
4337
	}
4338
 // CommonJS 
4339
	if (typeof exports !== 'undefined') {
4340
      exports.jsPlumb = jsPlumb;
4341
  	}
4342
	
4343
	
4344
// --------------------- end static instance + AMD registration -------------------------------------------		
4345
	
4346
})();
4347
4348
4349
;(function() {
4350
        
4351
    // create the drag handler for a connection
4352
    var _makeConnectionDragHandler = function(placeholder, _jsPlumb) {
4353
        var stopped = false;
4354
        return {
4355
            drag : function() {
4356
                if (stopped) {
4357
                    stopped = false;
4358
                    return true;
4359
                }
4360
                var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments, _jsPlumb.getZoom());
4361
        
4362
                if (placeholder.element) {
4363
                    jsPlumb.CurrentLibrary.setOffset(placeholder.element, _ui);                    
4364
                    _jsPlumb.repaint(placeholder.element, _ui);
4365
                }
4366
            },
4367
            stopDrag : function() {
4368
                stopped = true;
4369
            }
4370
        };
4371
    };
4372
        
4373
    // creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset.    
4374
    var _makeDraggablePlaceholder = function(placeholder, parent, _jsPlumb) {
4375
        var n = document.createElement("div");
4376
        n.style.position = "absolute";
4377
        var placeholderDragElement = jsPlumb.CurrentLibrary.getElementObject(n);
4378
        jsPlumb.CurrentLibrary.appendElement(n, parent);
4379
        var id = _jsPlumb.getId(n);
4380
        _jsPlumb.updateOffset( { elId : id });
4381
        // create and assign an id, and initialize the offset.
4382
        placeholder.id = id;
4383
        placeholder.element = n;
4384
    };
4385
    
4386
    // create a floating endpoint (for drag connections)
4387
    var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement, _jsPlumb, _newEndpoint) {			
4388
        var floatingAnchor = new jsPlumb.FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas, jsPlumbInstance:_jsPlumb });
4389
        //setting the scope here should not be the way to fix that mootools issue.  it should be fixed by not
4390
        // adding the floating endpoint as a droppable.  that makes more sense anyway!
4391
        return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" });
4392
    };
4393
4394
    var typeParameters = [ "connectorStyle", "connectorHoverStyle", "connectorOverlays",
4395
                "connector", "connectionType", "connectorClass", "connectorHoverClass" ];
4396
4397
    // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null,
4398
    // or no connection to it is found, we return the first connection in our list.
4399
    var findConnectionToUseForDynamicAnchor = function(ep, elementWithPrecedence) {
4400
        var idx = 0;
4401
        if (elementWithPrecedence != null) {
4402
            for (var i = 0; i < ep.connections.length; i++) {
4403
                if (ep.connections[i].sourceId == elementWithPrecedence || ep.connections[i].targetId == elementWithPrecedence) {
4404
                    idx = i;
4405
                    break;
4406
                }
4407
            }
4408
        }
4409
        
4410
        return ep.connections[idx];
4411
    };
4412
4413
    var findConnectionIndex = function(conn, ep) {
4414
        return jsPlumbUtil.findWithFunction(ep.connections, function(c) { return c.id == conn.id; });
4415
    };
4416
4417
    jsPlumb.Endpoint = function(params) {
4418
        var _jsPlumb = params._jsPlumb,
4419
            jpcl = jsPlumb.CurrentLibrary,
4420
            _att = jsPlumbAdapter.getAttribute,
4421
            _gel = jpcl.getElementObject,
4422
            _dom = jpcl.getDOMElement,
4423
            _ju = jsPlumbUtil,            
4424
            _newConnection = params.newConnection,
4425
            _newEndpoint = params.newEndpoint,
4426
            _finaliseConnection = params.finaliseConnection,
4427
            _fireDetachEvent = params.fireDetachEvent,
4428
            _fireMoveEvent = params.fireMoveEvent,
4429
            floatingConnections = params.floatingConnections;
4430
        
4431
        this.idPrefix = "_jsplumb_e_";			
4432
        this.defaultLabelLocation = [ 0.5, 0.5 ];
4433
        this.defaultOverlayKeys = ["Overlays", "EndpointOverlays"];
4434
        this.parent = params.parent;
4435
        OverlayCapableJsPlumbUIComponent.apply(this, arguments);        
4436
        
4437
// TYPE		
4438
                
4439
        this.getDefaultType = function() {								
4440
            return {
4441
                parameters:{},
4442
                scope:null,
4443
                maxConnections:this._jsPlumb.instance.Defaults.MaxConnections,
4444
                paintStyle:this._jsPlumb.instance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle,
4445
                endpoint:this._jsPlumb.instance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint,
4446
                hoverPaintStyle:this._jsPlumb.instance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle,				
4447
                overlays:this._jsPlumb.instance.Defaults.EndpointOverlays || jsPlumb.Defaults.EndpointOverlays,
4448
                connectorStyle:params.connectorStyle,				
4449
                connectorHoverStyle:params.connectorHoverStyle,
4450
                connectorClass:params.connectorClass,
4451
                connectorHoverClass:params.connectorHoverClass,
4452
                connectorOverlays:params.connectorOverlays,
4453
                connector:params.connector,
4454
                connectorTooltip:params.connectorTooltip
4455
            };
4456
        };
4457
        			
4458
// END TYPE
4459
            
4460
        this._jsPlumb.enabled = !(params.enabled === false);
4461
        this._jsPlumb.visible = true;        
4462
        this.element = _dom(params.source);  
4463
        this._jsPlumb.uuid = params.uuid;
4464
        this._jsPlumb.floatingEndpoint = null;  
4465
        var inPlaceCopy = null;
4466
        if (this._jsPlumb.uuid) params.endpointsByUUID[this._jsPlumb.uuid] = this;
4467
        this.elementId = params.elementId;
4468
        
4469
        this._jsPlumb.connectionCost = params.connectionCost;
4470
        this._jsPlumb.connectionsDirected = params.connectionsDirected;        
4471
        this._jsPlumb.currentAnchorClass = "";
4472
        this._jsPlumb.events = {};
4473
            
4474
        var  _updateAnchorClass = function() {
4475
            jpcl.removeClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
4476
            this.removeClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
4477
            this._jsPlumb.currentAnchorClass = this.anchor.getCssClass();
4478
            this.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
4479
            jpcl.addClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
4480
        }.bind(this);
4481
        
4482
        this.setAnchor = function(anchorParams, doNotRepaint) {
4483
            this._jsPlumb.instance.continuousAnchorFactory.clear(this.elementId);
4484
            this.anchor = this._jsPlumb.instance.makeAnchor(anchorParams, this.elementId, _jsPlumb);
4485
            _updateAnchorClass();
4486
            this.anchor.bind("anchorChanged", function(currentAnchor) {
4487
                this.fire("anchorChanged", {endpoint:this, anchor:currentAnchor});
4488
                _updateAnchorClass();
4489
            }.bind(this));
4490
            if (!doNotRepaint)
4491
                this._jsPlumb.instance.repaint(this.elementId);
4492
            return this;
4493
        };
4494
4495
        var anchorParamsToUse = params.anchor ? params.anchor : params.anchors ? params.anchors : (_jsPlumb.Defaults.Anchor || "Top");
4496
        this.setAnchor(anchorParamsToUse, true);
4497
4498
        // endpoint delegates to first connection for hover, if there is one.
4499
        var internalHover = function(state) {
4500
          if (this.connections.length > 0)
4501
            this.connections[0].setHover(state, false);
4502
          else
4503
            this.setHover(state);
4504
        }.bind(this);
4505
            
4506
        // ANCHOR MANAGER
4507
        if (!params._transient) // in place copies, for example, are transient.  they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted.
4508
            this._jsPlumb.instance.anchorManager.add(this, this.elementId);
4509
        
4510
        this.setEndpoint = function(ep) {
4511
4512
            if (this.endpoint != null) {
4513
                this.endpoint.cleanup();
4514
                this.endpoint.destroy();
4515
            }
4516
4517
            var _e = function(t, p) {
4518
                var rm = _jsPlumb.getRenderMode();
4519
                if (jsPlumb.Endpoints[rm][t]) return new jsPlumb.Endpoints[rm][t](p);
4520
                if (!_jsPlumb.Defaults.DoNotThrowErrors)
4521
                    throw { msg:"jsPlumb: unknown endpoint type '" + t + "'" };
4522
            };            
4523
4524
            var endpointArgs = {
4525
                _jsPlumb:this._jsPlumb.instance,
4526
                cssClass:params.cssClass,
4527
                parent:params.parent,
4528
                container:params.container,
4529
                tooltip:params.tooltip,
4530
                connectorTooltip:params.connectorTooltip,
4531
                endpoint:this
4532
            };
4533
            if (_ju.isString(ep)) 
4534
                this.endpoint = _e(ep, endpointArgs);
4535
            else if (_ju.isArray(ep)) {
4536
                endpointArgs = _ju.merge(ep[1], endpointArgs);
4537
                this.endpoint = _e(ep[0], endpointArgs);
4538
            }
4539
            else {
4540
                this.endpoint = ep.clone();
4541
            }
4542
4543
            // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned,
4544
            // and the clone is left in its place while the original one goes off on a magical journey. 
4545
            // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by
4546
            // the whole world.
4547
            var argsForClone = jsPlumb.extend({}, endpointArgs);						
4548
            this.endpoint.clone = function() {
4549
                // TODO this, and the code above, can be refactored to be more dry.
4550
                if (_ju.isString(ep)) 
4551
                    return _e(ep, endpointArgs);
4552
                else if (_ju.isArray(ep)) {
4553
                    endpointArgs = _ju.merge(ep[1], endpointArgs);
4554
                    return _e(ep[0], endpointArgs);
4555
                }
4556
            }.bind(this);
4557
4558
            this.type = this.endpoint.type;
4559
            // bind listeners from endpoint to self, with the internal hover function defined above.
4560
            this.bindListeners(this.endpoint, this, internalHover);
4561
        };
4562
         
4563
        this.setEndpoint(params.endpoint || _jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot");							                    
4564
        this.setPaintStyle(params.paintStyle || params.style || _jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, true);
4565
        this.setHoverPaintStyle(params.hoverPaintStyle || _jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle, true);
4566
        this._jsPlumb.paintStyleInUse = this.getPaintStyle();
4567
4568
        _ju.copyValues(typeParameters, params, this);        
4569
4570
        this.isSource = params.isSource || false;
4571
        this.isTarget = params.isTarget || false;        
4572
        this._jsPlumb.maxConnections = params.maxConnections || _jsPlumb.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of.                
4573
        this.canvas = this.endpoint.canvas;		
4574
        // add anchor class (need to do this on construction because we set anchor first)
4575
        this.addClass(_jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);	
4576
        jpcl.addClass(this.element, _jsPlumb.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);
4577
        this.connections = params.connections || [];
4578
        this.connectorPointerEvents = params["connector-pointer-events"];
4579
        
4580
        this.scope = params.scope || _jsPlumb.getDefaultScope();        
4581
        this.timestamp = null;
4582
        this.reattachConnections = params.reattach || _jsPlumb.Defaults.ReattachConnections;
4583
        this.connectionsDetachable = _jsPlumb.Defaults.ConnectionsDetachable;
4584
        if (params.connectionsDetachable === false || params.detachable === false)
4585
            this.connectionsDetachable = false;
4586
        this.dragAllowedWhenFull = params.dragAllowedWhenFull || true;
4587
        
4588
        if (params.onMaxConnections)
4589
            this.bind("maxConnections", params.onMaxConnections);        
4590
        
4591
        //
4592
        // add a connection. not part of public API.
4593
        //
4594
        this.addConnection = function(connection) {
4595
            this.connections.push(connection);                  
4596
            this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);       
4597
            this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass); 
4598
        };	
4599
        
4600
        this.detachFromConnection = function(connection, idx) {
4601
            idx = idx == null ? findConnectionIndex(connection, this) : idx;
4602
            if (idx >= 0) {
4603
                this.connections.splice(idx, 1);
4604
                this[(this.connections.length > 0 ? "add" : "remove") + "Class"](_jsPlumb.endpointConnectedClass);       
4605
                this[(this.isFull() ? "add" : "remove") + "Class"](_jsPlumb.endpointFullClass);                 
4606
            }
4607
        };
4608
4609
        this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent, endpointBeingDeleted, connectionIndex) {
4610
4611
            var idx = connectionIndex == null ? findConnectionIndex(connection, this) : connectionIndex,
4612
                actuallyDetached = false;
4613
                fireEvent = (fireEvent !== false);
4614
4615
            if (idx >= 0) {		                
4616
                if (forceDetach || connection._forceDetach || (connection.isDetachable() && connection.isDetachAllowed(connection) && this.isDetachAllowed(connection) )) {
4617
4618
                    //connection.setHover(false);
4619
4620
                    _jsPlumb.deleteObject({
4621
                        connection:connection, 
4622
                        fireEvent:(!ignoreTarget && fireEvent), 
4623
                        originalEvent:originalEvent
4624
                    });
4625
                    actuallyDetached = true;                       
4626
                }
4627
            }
4628
            return actuallyDetached;
4629
        };	
4630
4631
        this.detachAll = function(fireEvent, originalEvent) {
4632
            while (this.connections.length > 0) {
4633
                // TODO this could pass the index in to the detach method to save some time (index will always be zero in this while loop)
4634
                // TODO now it defaults to fireEvent true.  will that mess with things?
4635
                this.detach(this.connections[0], false, true, fireEvent !== false, originalEvent, this, 0);
4636
            }
4637
            return this;
4638
        };                
4639
        this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) {
4640
            var c = [];
4641
            for ( var i = 0; i < this.connections.length; i++) {
4642
                if (this.connections[i].endpoints[1] == targetEndpoint || this.connections[i].endpoints[0] == targetEndpoint) {
4643
                    c.push(this.connections[i]);
4644
                }
4645
            }
4646
            for ( var j = 0; j < c.length; j++) {
4647
                this.detach(c[j], false, true, fireEvent, originalEvent);				
4648
            }
4649
            return this;
4650
        };	        
4651
        
4652
        this.getElement = function() {
4653
            return this.element;
4654
        };		
4655
                 
4656
        // container not supported in 1.5.5; you cannot change the container once it is set.
4657
        // it might come back int a future release.
4658
        this.setElement = function(el/*, container*/) {
4659
            var parentId = this._jsPlumb.instance.getId(el),
4660
                curId = this.elementId;
4661
            // remove the endpoint from the list for the current endpoint's element
4662
            _ju.removeWithFunction(params.endpointsByElement[this.elementId], function(e) {
4663
                return e.id == this.id;
4664
            }.bind(this));
4665
            this.element = _dom(el);
4666
            this.elementId = _jsPlumb.getId(this.element);                         
4667
            _jsPlumb.anchorManager.rehomeEndpoint(this, curId, this.element);
4668
            _jsPlumb.dragManager.endpointAdded(this.element);            
4669
            _ju.addToList(params.endpointsByElement, parentId, this);            
4670
            return this;
4671
        };
4672
                
4673
        /**
4674
         * private but must be exposed.
4675
         */
4676
        this.makeInPlaceCopy = function() {
4677
            var loc = this.anchor.getCurrentLocation({element:this}),
4678
                o = this.anchor.getOrientation(this),
4679
                acc = this.anchor.getCssClass(),
4680
                inPlaceAnchor = {
4681
                    bind:function() { },
4682
                    compute:function() { return [ loc[0], loc[1] ]; },
4683
                    getCurrentLocation : function() { return [ loc[0], loc[1] ]; },
4684
                    getOrientation:function() { return o; },
4685
                    getCssClass:function() { return acc; }
4686
                };
4687
4688
            return _newEndpoint( { 
4689
                anchor : inPlaceAnchor, 
4690
                source : this.element, 
4691
                paintStyle : this.getPaintStyle(), 
4692
                endpoint : params.hideOnDrag ? "Blank" : this.endpoint,
4693
                _transient:true,
4694
                scope:this.scope
4695
            });
4696
        };
4697
                
4698
4699
        /**
4700
         * private but needs to be exposed.
4701
         */
4702
        this.isFloating = function() {
4703
            return this.anchor != null && this.anchor.isFloating;
4704
        };
4705
        
4706
        /**
4707
         * returns a connection from the pool; used when dragging starts.  just gets the head of the array if it can.
4708
         */
4709
        this.connectorSelector = function() {
4710
            var candidate = this.connections[0];
4711
            if (this.isTarget && candidate) return candidate;
4712
            else {
4713
                return (this.connections.length < this._jsPlumb.maxConnections) || this._jsPlumb.maxConnections == -1 ? null : candidate;
4714
            }
4715
        };        
4716
        
4717
        this.setStyle = this.setPaintStyle;        
4718
        
4719
        this.paint = function(params) {
4720
            params = params || {};
4721
            var timestamp = params.timestamp, recalc = !(params.recalc === false);								
4722
            if (!timestamp || this.timestamp !== timestamp) {						
4723
                
4724
                // TODO check: is this is a safe performance enhancement?
4725
                var info = _jsPlumb.updateOffset({ elId:this.elementId, timestamp:timestamp/*, recalc:recalc*/ });                
4726
4727
                var xy = params.offset ? params.offset.o : info.o;
4728
                if(xy != null) {
4729
                    var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle;
4730
                    if (ap == null) {
4731
                        var wh = params.dimensions || info.s,                       
4732
                            anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : this, timestamp : timestamp };
4733
                        if (recalc && this.anchor.isDynamic && this.connections.length > 0) {
4734
                            var c = findConnectionToUseForDynamicAnchor(this, params.elementWithPrecedence),
4735
                                oIdx = c.endpoints[0] == this ? 1 : 0,
4736
                                oId = oIdx === 0 ? c.sourceId : c.targetId,
4737
                                oInfo = _jsPlumb.getCachedData(oId),
4738
                                oOffset = oInfo.o, oWH = oInfo.s;
4739
                            anchorParams.txy = [ oOffset.left, oOffset.top ];
4740
                            anchorParams.twh = oWH;
4741
                            anchorParams.tElement = c.endpoints[oIdx];
4742
                        }
4743
                        ap = this.anchor.compute(anchorParams);
4744
                    }
4745
                                        
4746
                    this.endpoint.compute(ap, this.anchor.getOrientation(this), this._jsPlumb.paintStyleInUse, connectorPaintStyle || this.paintStyleInUse);
4747
                    this.endpoint.paint(this._jsPlumb.paintStyleInUse, this.anchor);					
4748
                    this.timestamp = timestamp;
4749
4750
                    // paint overlays
4751
                    for ( var i = 0; i < this._jsPlumb.overlays.length; i++) {
4752
                        var o = this._jsPlumb.overlays[i];
4753
                        if (o.isVisible()) { 
4754
                            this._jsPlumb.overlayPlacements[i] = o.draw(this.endpoint, this._jsPlumb.paintStyleInUse);
4755
                            o.paint(this._jsPlumb.overlayPlacements[i]);    
4756
                        }
4757
                    }
4758
                }
4759
            }
4760
        };
4761
4762
        this.repaint = this.paint; 
4763
4764
        var draggingInitialised = false;
4765
        this.initDraggable = function() {
4766
            // is this a connection source? we make it draggable and have the
4767
            // drag listener maintain a connection with a floating endpoint.
4768
            if (!draggingInitialised && jpcl.isDragSupported(this.element)) {
4769
                var placeholderInfo = { id:null, element:null },
4770
                    jpc = null,
4771
                    existingJpc = false,
4772
                    existingJpcParams = null,
4773
                    _dragHandler = _makeConnectionDragHandler(placeholderInfo, _jsPlumb);
4774
4775
                var start = function() {    
4776
                // drag might have started on an endpoint that is not actually a source, but which has
4777
                // one or more connections.
4778
                    jpc = this.connectorSelector();
4779
                    var _continue = true;
4780
                    // if not enabled, return
4781
                    if (!this.isEnabled()) _continue = false;
4782
                    // if no connection and we're not a source, return.
4783
                    if (jpc == null && !this.isSource) _continue = false;
4784
                    // otherwise if we're full and not allowed to drag, also return false.
4785
                    if (this.isSource && this.isFull() && !this.dragAllowedWhenFull) _continue = false;
4786
                    // if the connection was setup as not detachable or one of its endpoints
4787
                    // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable
4788
                    // is set to false...
4789
                    if (jpc != null && !jpc.isDetachable()) _continue = false;
4790
4791
                    if (_continue === false) {
4792
                        // this is for mootools and yui. returning false from this causes jquery to stop drag.
4793
                        // the events are wrapped in both mootools and yui anyway, but i don't think returning
4794
                        // false from the start callback would stop a drag.
4795
                        if (jpcl.stopDrag) jpcl.stopDrag();
4796
                        _dragHandler.stopDrag();
4797
                        return false;
4798
                    }
4799
4800
                    // clear hover for all connections for this endpoint before continuing.
4801
                    for (var i = 0; i < this.connections.length; i++)
4802
                        this.connections[i].setHover(false);
4803
4804
                    this.addClass("endpointDrag");
4805
                    _jsPlumb.setConnectionBeingDragged(true);
4806
4807
                    // if we're not full but there was a connection, make it null. we'll create a new one.
4808
                    if (jpc && !this.isFull() && this.isSource) jpc = null;
4809
4810
                    _jsPlumb.updateOffset( { elId : this.elementId });
4811
                    inPlaceCopy = this.makeInPlaceCopy();
4812
                    inPlaceCopy.referenceEndpoint = this;
4813
                    inPlaceCopy.paint();                                                                
4814
                    
4815
                    _makeDraggablePlaceholder(placeholderInfo, this.parent, _jsPlumb);
4816
                    
4817
                    // set the offset of this div to be where 'inPlaceCopy' is, to start with.
4818
                    // TODO merge this code with the code in both Anchor and FloatingAnchor, because it
4819
                    // does the same stuff.
4820
                    var ipcoel = _gel(inPlaceCopy.canvas),
4821
                        ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel, _jsPlumb),
4822
                        po = _jsPlumb.adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas),
4823
                        canvasElement = _gel(this.canvas);                               
4824
                        
4825
                    jpcl.setOffset(placeholderInfo.element, {left:po[0], top:po[1]});                                                           
4826
                    
4827
                    // when using makeSource and a parent, we first draw the source anchor on the source element, then
4828
                    // move it to the parent.  note that this happens after drawing the placeholder for the
4829
                    // first time.
4830
                    if (this.parentAnchor) this.anchor = _jsPlumb.makeAnchor(this.parentAnchor, this.elementId, _jsPlumb);
4831
                    
4832
                    // store the id of the dragging div and the source element. the drop function will pick these up.                   
4833
                    _jsPlumb.setAttribute(this.canvas, "dragId", placeholderInfo.id);
4834
                    _jsPlumb.setAttribute(this.canvas, "elId", this.elementId);
4835
4836
                    this._jsPlumb.floatingEndpoint = _makeFloatingEndpoint(this.getPaintStyle(), this.anchor, this.endpoint, this.canvas, placeholderInfo.element, _jsPlumb, _newEndpoint);
4837
                    // TODO we should not know about DOM here. make the library adapter do this (or the 
4838
                        // dom adapter)
4839
                    this.canvas.style.visibility = "hidden";            
4840
                    
4841
                    if (jpc == null) {                                                                                                                                                         
4842
                        this.anchor.locked = true;
4843
                        this.setHover(false, false);                        
4844
                        // create a connection. one end is this endpoint, the other is a floating endpoint.                    
4845
                        jpc = _newConnection({
4846
                            sourceEndpoint : this,
4847
                            targetEndpoint : this._jsPlumb.floatingEndpoint,
4848
                            source : this.endpointWillMoveTo || this.element,  // for makeSource with parent option.  ensure source element is represented correctly.
4849
                            target : placeholderInfo.element,
4850
                            anchors : [ this.anchor, this._jsPlumb.floatingEndpoint.anchor ],
4851
                            paintStyle : params.connectorStyle, // this can be null. Connection will use the default.
4852
                            hoverPaintStyle:params.connectorHoverStyle,
4853
                            connector : params.connector, // this can also be null. Connection will use the default.
4854
                            overlays : params.connectorOverlays,
4855
                            type:this.connectionType,
4856
                            cssClass:this.connectorClass,
4857
                            hoverClass:this.connectorHoverClass
4858
                        });
4859
                        jpc.pending = true; // mark this connection as not having been established.
4860
                        jpc.addClass(_jsPlumb.draggingClass);
4861
                        this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass);
4862
                        // fire an event that informs that a connection is being dragged                        
4863
                        _jsPlumb.fire("connectionDrag", jpc);
4864
4865
                    } else {
4866
                        existingJpc = true;
4867
                        jpc.setHover(false);                        
4868
                        // if existing connection, allow to be dropped back on the source endpoint (issue 51).
4869
                        _initDropTarget(ipcoel, false, true);
4870
                        // new anchor idx
4871
                        var anchorIdx = jpc.endpoints[0].id == this.id ? 0 : 1;
4872
                        jpc.floatingAnchorIndex = anchorIdx;                    // save our anchor index as the connection's floating index.                        
4873
                        this.detachFromConnection(jpc);                         // detach from the connection while dragging is occurring.
4874
                        
4875
                        // store the original scope (issue 57)
4876
                        var dragScope = jsPlumb.CurrentLibrary.getDragScope(canvasElement);
4877
                        _jsPlumb.setAttribute(this.canvas, "originalScope", dragScope);
4878
                        // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones
4879
                        // that have our drop scope (issue 57).
4880
                        var dropScope = jpcl.getDropScope(canvasElement);
4881
                        jpcl.setDragScope(canvasElement, dropScope);
4882
4883
                        // fire an event that informs that a connection is being dragged. we do this before
4884
                        // replacing the original target with the floating element info.
4885
                        _jsPlumb.fire("connectionDrag", jpc);
4886
                
4887
                        // now we replace ourselves with the temporary div we created above:
4888
                        if (anchorIdx === 0) {
4889
                            existingJpcParams = [ jpc.source, jpc.sourceId, canvasElement, dragScope ];
4890
                            jpc.source = placeholderInfo.element;
4891
                            jpc.sourceId = placeholderInfo.id;
4892
                        } else {
4893
                            existingJpcParams = [ jpc.target, jpc.targetId, canvasElement, dragScope ];
4894
                            jpc.target = placeholderInfo.element;
4895
                            jpc.targetId = placeholderInfo.id;
4896
                        }
4897
4898
                        // lock the other endpoint; if it is dynamic it will not move while the drag is occurring.
4899
                        jpc.endpoints[anchorIdx === 0 ? 1 : 0].anchor.locked = true;
4900
                        // store the original endpoint and assign the new floating endpoint for the drag.
4901
                        jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
4902
                        
4903
                        // PROVIDE THE SUSPENDED ELEMENT, BE IT A SOURCE OR TARGET (ISSUE 39)
4904
                        jpc.suspendedElement = jpc.endpoints[anchorIdx].getElement();
4905
                        jpc.suspendedElementId = jpc.endpoints[anchorIdx].elementId;
4906
                        jpc.suspendedElementType = anchorIdx === 0 ? "source" : "target";
4907
                        
4908
                        jpc.suspendedEndpoint.setHover(false);
4909
                        this._jsPlumb.floatingEndpoint.referenceEndpoint = jpc.suspendedEndpoint;
4910
                        jpc.endpoints[anchorIdx] = this._jsPlumb.floatingEndpoint;
4911
4912
                        jpc.addClass(_jsPlumb.draggingClass);
4913
                        this._jsPlumb.floatingEndpoint.addClass(_jsPlumb.draggingClass);                    
4914
4915
                    }
4916
                    // register it and register connection on it.
4917
                    floatingConnections[placeholderInfo.id] = jpc;
4918
                    _jsPlumb.anchorManager.addFloatingConnection(placeholderInfo.id, jpc);               
4919
                    // only register for the target endpoint; we will not be dragging the source at any time
4920
                    // before this connection is either discarded or made into a permanent connection.
4921
                    _ju.addToList(params.endpointsByElement, placeholderInfo.id, this._jsPlumb.floatingEndpoint);
4922
                    // tell jsplumb about it
4923
                    _jsPlumb.currentlyDragging = true;
4924
                }.bind(this);
4925
4926
                var dragOptions = params.dragOptions || {},
4927
                    defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions),
4928
                    startEvent = jpcl.dragEvents.start,
4929
                    stopEvent = jpcl.dragEvents.stop,
4930
                    dragEvent = jpcl.dragEvents.drag;
4931
                
4932
                dragOptions = jsPlumb.extend(defaultOpts, dragOptions);
4933
                dragOptions.scope = dragOptions.scope || this.scope;
4934
                dragOptions[startEvent] = _ju.wrap(dragOptions[startEvent], start, false);
4935
                // extracted drag handler function so can be used by makeSource
4936
                dragOptions[dragEvent] = _ju.wrap(dragOptions[dragEvent], _dragHandler.drag);
4937
                dragOptions[stopEvent] = _ju.wrap(dragOptions[stopEvent],
4938
                    function() {        
4939
4940
                        _jsPlumb.setConnectionBeingDragged(false);  
4941
                        // if no endpoints, jpc already cleaned up.
4942
                        if (jpc.endpoints != null) {          
4943
                            // get the actual drop event (decode from library args to stop function)
4944
                            var originalEvent = jpcl.getDropEvent(arguments);                                       
4945
                            // unlock the other endpoint (if it is dynamic, it would have been locked at drag start)
4946
                            var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
4947
                            jpc.endpoints[idx === 0 ? 1 : 0].anchor.locked = false;
4948
                            // WHY does this need to happen?  i suppose because the connection might not get 
4949
                            // deleted.  TODO: i dont want to know about css classes inside jsplumb, ideally.
4950
                            jpc.removeClass(_jsPlumb.draggingClass);   
4951
                        
4952
                            // if we have the floating endpoint then the connection has not been dropped
4953
                            // on another endpoint.  If it is a new connection we throw it away. If it is an 
4954
                            // existing connection we check to see if we should reattach it, throwing it away 
4955
                            // if not.
4956
                            if (jpc.endpoints[idx] == this._jsPlumb.floatingEndpoint) {
4957
                                // 6a. if the connection was an existing one...
4958
                                if (existingJpc && jpc.suspendedEndpoint) {
4959
                                    // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the
4960
                                    // floating endpoint has been replaced.
4961
                                    if (idx === 0) {
4962
                                        jpc.source = existingJpcParams[0];
4963
                                        jpc.sourceId = existingJpcParams[1];
4964
                                    } else {
4965
                                        jpc.target = existingJpcParams[0];
4966
                                        jpc.targetId = existingJpcParams[1];
4967
                                    }
4968
                                    
4969
                                    // restore the original scope (issue 57)
4970
                                    jpcl.setDragScope(existingJpcParams[2], existingJpcParams[3]);
4971
                                    jpc.endpoints[idx] = jpc.suspendedEndpoint;
4972
                                    // IF the connection should be reattached, or the other endpoint refuses detach, then
4973
                                    // reset the connection to its original state
4974
                                    if (jpc.isReattach() || jpc._forceReattach || jpc._forceDetach || !jpc.endpoints[idx === 0 ? 1 : 0].detach(jpc, false, false, true, originalEvent)) {                                   
4975
                                        jpc.setHover(false);
4976
                                        jpc.floatingAnchorIndex = null;
4977
                                        jpc._forceDetach = null;
4978
                                        jpc._forceReattach = null;
4979
                                        this._jsPlumb.floatingEndpoint.detachFromConnection(jpc);
4980
                                        jpc.suspendedEndpoint.addConnection(jpc);
4981
                                        _jsPlumb.repaint(existingJpcParams[1]);
4982
                                    }
4983
                                }                                                               
4984
                            }
4985
                        }
4986
4987
                        // remove the element associated with the floating endpoint 
4988
                        // (and its associated floating endpoint and visual artefacts)                                        
4989
                        _jsPlumb.remove(placeholderInfo.element, false);
4990
                        // remove the inplace copy
4991
                        _jsPlumb.remove(inPlaceCopy.canvas, false);
4992
4993
                        // makeTargets sets this flag, to tell us we have been replaced and should delete ourself.
4994
                        if (this.deleteAfterDragStop) {                        
4995
                            _jsPlumb.deleteObject({endpoint:this});
4996
                        }
4997
                        else {
4998
                            if (this._jsPlumb) {
4999
                                this._jsPlumb.floatingEndpoint = null;
5000
                                // repaint this endpoint.
5001
                                // make our canvas visible (TODO: hand off to library; we should not know about DOM)
5002
                                this.canvas.style.visibility = "visible";
5003
                                // unlock our anchor
5004
                                this.anchor.locked = false;
5005
                                this.paint({recalc:false});                        
5006
                            }
5007
                        }                                                    
5008
5009
                        // although the connection is no longer valid, there are use cases where this is useful.
5010
                        _jsPlumb.fire("connectionDragStop", jpc, originalEvent);
5011
5012
                        // tell jsplumb that dragging is finished.
5013
                        _jsPlumb.currentlyDragging = false;
5014
5015
                        jpc = null;
5016
5017
                    }.bind(this));
5018
                
5019
                var i = _gel(this.canvas);              
5020
                jpcl.initDraggable(i, dragOptions, true, _jsPlumb);
5021
5022
                draggingInitialised = true;
5023
            }
5024
        };
5025
5026
        // if marked as source or target at create time, init the dragging.
5027
        if (this.isSource || this.isTarget)
5028
            this.initDraggable();        
5029
5030
        // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections
5031
        // back onto the endpoint you detached it from.
5032
        var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) {
5033
            if ((this.isTarget || forceInit) && jpcl.isDropSupported(this.element)) {
5034
                var dropOptions = params.dropOptions || _jsPlumb.Defaults.DropOptions || jsPlumb.Defaults.DropOptions;
5035
                dropOptions = jsPlumb.extend( {}, dropOptions);
5036
                dropOptions.scope = dropOptions.scope || this.scope;
5037
                var dropEvent = jpcl.dragEvents.drop,
5038
                    overEvent = jpcl.dragEvents.over,
5039
                    outEvent = jpcl.dragEvents.out,
5040
                    drop = function() {                        
5041
5042
                        this.removeClass(_jsPlumb.endpointDropAllowedClass);
5043
                        this.removeClass(_jsPlumb.endpointDropForbiddenClass);
5044
                                                    
5045
                        var originalEvent = jpcl.getDropEvent(arguments),
5046
                            draggable = _gel(jpcl.getDragObject(arguments)),
5047
                            id = _jsPlumb.getAttribute(draggable, "dragId"),
5048
                            elId = _jsPlumb.getAttribute(draggable, "elId"),						
5049
                            scope = _jsPlumb.getAttribute(draggable, "originalScope"),
5050
                            jpc = floatingConnections[id];
5051
                            
5052
                        // if this is a drop back where the connection came from, mark it force rettach and
5053
                        // return; the stop handler will reattach. without firing an event.
5054
                        var redrop = jpc.suspendedEndpoint && (jpc.suspendedEndpoint.id == this.id ||
5055
                                        this.referenceEndpoint && jpc.suspendedEndpoint.id == this.referenceEndpoint.id) ;							
5056
                        if (redrop) {								
5057
                            jpc._forceReattach = true;
5058
                            return;
5059
                        }
5060
5061
                        if (jpc != null) {
5062
                            var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx === 0 ? 1 : 0;
5063
                            
5064
                            // restore the original scope if necessary (issue 57)						
5065
                            if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope);							
5066
                            
5067
                            var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true;
5068
                            
5069
                            if (this.isFull()) {
5070
                                this.fire("maxConnections", { 
5071
                                    endpoint:this, 
5072
                                    connection:jpc, 
5073
                                    maxConnections:this._jsPlumb.maxConnections 
5074
                                }, originalEvent);
5075
                            }
5076
                                                            
5077
                            if (!this.isFull() && !(idx === 0 && !this.isSource) && !(idx == 1 && !this.isTarget) && endpointEnabled) {
5078
                                var _doContinue = true;
5079
5080
                                // the second check here is for the case that the user is dropping it back
5081
                                // where it came from.
5082
                                if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != this.id) {
5083
                                    if (idx === 0) {
5084
                                        jpc.source = jpc.suspendedEndpoint.element;
5085
                                        jpc.sourceId = jpc.suspendedEndpoint.elementId;
5086
                                    } else {
5087
                                        jpc.target = jpc.suspendedEndpoint.element;
5088
                                        jpc.targetId = jpc.suspendedEndpoint.elementId;
5089
                                    }
5090
5091
                                    if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_jsPlumb.checkCondition("beforeDetach", jpc))
5092
                                        _doContinue = false;								
5093
                                }
5094
            
5095
                                // these have to be set before testing for beforeDrop.
5096
                                if (idx === 0) {
5097
                                    jpc.source = this.element;
5098
                                    jpc.sourceId = this.elementId;
5099
                                } else {
5100
                                    jpc.target = this.element;
5101
                                    jpc.targetId = this.elementId;
5102
                                }
5103
                                                            
5104
// ------------ wrap the execution path in a function so we can support asynchronous beforeDrop																
5105
                                    
5106
                                // we want to execute this regardless.
5107
                                var commonFunction = function() {
5108
                                    jpc.floatingAnchorIndex = null;
5109
                                };	
5110
                                                                                                
5111
                                var continueFunction = function() {
5112
                                    jpc.pending = false;
5113
5114
                                    // remove this jpc from the current endpoint
5115
                                    jpc.endpoints[idx].detachFromConnection(jpc);
5116
                                    if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc);
5117
                                    jpc.endpoints[idx] = this;
5118
                                    this.addConnection(jpc);
5119
                                    
5120
                                    // copy our parameters in to the connection:
5121
                                    var params = this.getParameters();
5122
                                    for (var aParam in params)
5123
                                        jpc.setParameter(aParam, params[aParam]);
5124
5125
                                    if (!jpc.suspendedEndpoint) {  
5126
                                        // if not an existing connection and
5127
                                        if (params.draggable)
5128
                                            jsPlumb.CurrentLibrary.initDraggable(this.element, dragOptions, true, _jsPlumb);
5129
                                    }
5130
                                    else {
5131
                                        var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
5132
                                        _fireMoveEvent({
5133
                                            index:idx,
5134
                                            originalSourceId:idx === 0 ? suspendedElementId : jpc.sourceId,
5135
                                            newSourceId:idx === 0 ? this.elementId : jpc.sourceId,
5136
                                            originalTargetId:idx == 1 ? suspendedElementId : jpc.targetId,
5137
                                            newTargetId:idx == 1 ? this.elementId : jpc.targetId,
5138
                                            originalSourceEndpoint:idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0],
5139
                                            newSourceEndpoint:idx === 0 ? this : jpc.endpoints[0],
5140
                                            originalTargetEndpoint:idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
5141
                                            newTargetEndpoint:idx == 1 ? this : jpc.endpoints[1],
5142
                                            connection:jpc
5143
                                        }, originalEvent);
5144
                                       /* var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
5145
                                        // fire a detach event
5146
                                        _fireDetachEvent({
5147
                                            source : idx === 0 ? suspendedElement : jpc.source, 
5148
                                            target : idx == 1 ? suspendedElement : jpc.target,
5149
                                            sourceId : idx === 0 ? suspendedElementId : jpc.sourceId, 
5150
                                            targetId : idx == 1 ? suspendedElementId : jpc.targetId,
5151
                                            sourceEndpoint : idx === 0 ? jpc.suspendedEndpoint : jpc.endpoints[0], 
5152
                                            targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
5153
                                            connection : jpc
5154
                                        }, true, originalEvent);*/
5155
                                    }
5156
5157
                                    // TODO this is like the makeTarget drop code.
5158
                                    if (idx == 1)
5159
                                        _jsPlumb.anchorManager.updateOtherEndpoint(jpc.sourceId, jpc.suspendedElementId, jpc.targetId, jpc);
5160
                                    else                                    
5161
                                        _jsPlumb.anchorManager.sourceChanged(jpc.suspendedEndpoint.elementId, jpc.sourceId, jpc);                                   
5162
5163
                                    // finalise will inform the anchor manager and also add to
5164
                                    // connectionsByScope if necessary.
5165
                                    // TODO if this is not set to true, then dragging a connection's target to a new
5166
                                    // target causes the connection to be forgotten. however if it IS set to true, then
5167
                                    // the opposite happens: dragging by source causes the connection to get forgotten
5168
                                    // about and then if you delete it jsplumb breaks.
5169
                                    _finaliseConnection(jpc, null, originalEvent/*, true*/);
5170
                                    
5171
                                    commonFunction();
5172
                                }.bind(this);
5173
                                
5174
                                var dontContinueFunction = function() {
5175
                                    // otherwise just put it back on the endpoint it was on before the drag.
5176
                                    if (jpc.suspendedEndpoint) {									
5177
                                        jpc.endpoints[idx] = jpc.suspendedEndpoint;
5178
                                        jpc.setHover(false);
5179
                                        jpc._forceDetach = true;
5180
                                        if (idx === 0) {
5181
                                            jpc.source = jpc.suspendedEndpoint.element;
5182
                                            jpc.sourceId = jpc.suspendedEndpoint.elementId;
5183
                                        } else {
5184
                                            jpc.target = jpc.suspendedEndpoint.element;
5185
                                            jpc.targetId = jpc.suspendedEndpoint.elementId;
5186
                                        }
5187
                                        jpc.suspendedEndpoint.addConnection(jpc);
5188
5189
                                        jpc.endpoints[0].repaint();
5190
                                        jpc.repaint();
5191
                                        _jsPlumb.repaint(jpc.sourceId);
5192
                                        jpc._forceDetach = false;
5193
                                    }
5194
                                    
5195
                                    commonFunction();
5196
                                };
5197
                                
5198
// --------------------------------------
5199
                                // now check beforeDrop.  this will be available only on Endpoints that are setup to
5200
                                // have a beforeDrop condition (although, secretly, under the hood all Endpoints and 
5201
                                // the Connection have them, because they are on jsPlumbUIComponent.  shhh!), because
5202
                                // it only makes sense to have it on a target endpoint.
5203
                                _doContinue = _doContinue && this.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope, jpc, this);
5204
                                                                                                                    
5205
                                if (_doContinue) {
5206
                                    continueFunction();
5207
                                }
5208
                                else {
5209
                                    dontContinueFunction();
5210
                                }
5211
                            }
5212
                            _jsPlumb.currentlyDragging = false;
5213
                        }
5214
                    }.bind(this);
5215
                
5216
                dropOptions[dropEvent] = _ju.wrap(dropOptions[dropEvent], drop);
5217
                dropOptions[overEvent] = _ju.wrap(dropOptions[overEvent], function() {					
5218
                    var draggable = jpcl.getDragObject(arguments),
5219
                        id = _jsPlumb.getAttribute(draggable, "dragId"),
5220
                        _jpc = floatingConnections[id];
5221
                        
5222
                    if (_jpc != null) {								
5223
                        var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex;
5224
                        // here we should fire the 'over' event if we are a target and this is a new connection,
5225
                        // or we are the same as the floating endpoint.								
5226
                        var _cont = (this.isTarget && _jpc.floatingAnchorIndex !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id == _jpc.suspendedEndpoint.id);
5227
                        if (_cont) {
5228
                            var bb = _jsPlumb.checkCondition("checkDropAllowed", { 
5229
                                sourceEndpoint:_jpc.endpoints[idx], 
5230
                                targetEndpoint:this,
5231
                                connection:_jpc
5232
                            }); 
5233
                            this[(bb ? "add" : "remove") + "Class"](_jsPlumb.endpointDropAllowedClass);
5234
                            this[(bb ? "remove" : "add") + "Class"](_jsPlumb.endpointDropForbiddenClass);
5235
                            _jpc.endpoints[idx].anchor.over(this.anchor, this);
5236
                        }
5237
                    }						
5238
                }.bind(this));	
5239
5240
                dropOptions[outEvent] = _ju.wrap(dropOptions[outEvent], function() {					
5241
                    var draggable = jpcl.getDragObject(arguments),
5242
                        id = _jsPlumb.getAttribute( draggable, "dragId"),
5243
                        _jpc = floatingConnections[id];
5244
                        
5245
                    if (_jpc != null) {
5246
                        var idx = _jpc.floatingAnchorIndex == null ? 1 : _jpc.floatingAnchorIndex;
5247
                        var _cont = (this.isTarget && _jpc.floatingAnchorIndex !== 0) || (_jpc.suspendedEndpoint && this.referenceEndpoint && this.referenceEndpoint.id == _jpc.suspendedEndpoint.id);
5248
                        if (_cont) {
5249
                            this.removeClass(_jsPlumb.endpointDropAllowedClass);
5250
                            this.removeClass(_jsPlumb.endpointDropForbiddenClass);
5251
                            _jpc.endpoints[idx].anchor.out();
5252
                        }
5253
                    }
5254
                }.bind(this));
5255
                jpcl.initDroppable(canvas, dropOptions, true, isTransient);
5256
            }
5257
        }.bind(this);
5258
        
5259
        // initialise the endpoint's canvas as a drop target.  this will be ignored if the endpoint is not a target or drag is not supported.
5260
        _initDropTarget(_gel(this.canvas), true, !(params._transient || this.anchor.isFloating), this);
5261
        
5262
         // finally, set type if it was provided
5263
         if (params.type)
5264
            this.addType(params.type, params.data, _jsPlumb.isSuspendDrawing());
5265
5266
        return this;        					
5267
    };
5268
5269
    jsPlumbUtil.extend(jsPlumb.Endpoint, OverlayCapableJsPlumbUIComponent, {
5270
        getTypeDescriptor : function() { return "endpoint"; },        
5271
        isVisible : function() { return this._jsPlumb.visible; },
5272
        setVisible : function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) {
5273
            this._jsPlumb.visible = v;
5274
            if (this.canvas) this.canvas.style.display = v ? "block" : "none";
5275
            this[v ? "showOverlays" : "hideOverlays"]();
5276
            if (!doNotChangeConnections) {
5277
                for (var i = 0; i < this.connections.length; i++) {
5278
                    this.connections[i].setVisible(v);
5279
                    if (!doNotNotifyOtherEndpoint) {
5280
                        var oIdx = this === this.connections[i].endpoints[0] ? 1 : 0;
5281
                        // only change the other endpoint if this is its only connection.
5282
                        if (this.connections[i].endpoints[oIdx].connections.length == 1) this.connections[i].endpoints[oIdx].setVisible(v, true, true);
5283
                    }
5284
                }
5285
            }
5286
        },
5287
        getAttachedElements : function() {
5288
            return this.connections;
5289
        },
5290
        applyType : function(t, doNotRepaint) {         
5291
            if (t.maxConnections != null) this._jsPlumb.maxConnections = t.maxConnections;
5292
            if (t.scope) this.scope = t.scope;
5293
            jsPlumbUtil.copyValues(typeParameters, t, this);
5294
            if (t.anchor) {
5295
                this.anchor = this._jsPlumb.instance.makeAnchor(t.anchor);
5296
            }
5297
        },
5298
        isEnabled : function() { return this._jsPlumb.enabled; },
5299
        setEnabled : function(e) { this._jsPlumb.enabled = e; },
5300
        cleanup : function() {            
5301
            jsPlumb.CurrentLibrary.removeClass(this.element, this._jsPlumb.instance.endpointAnchorClassPrefix + "_" + this._jsPlumb.currentAnchorClass);            
5302
            this.anchor = null;
5303
            this.endpoint.cleanup();
5304
            this.endpoint.destroy();
5305
            this.endpoint = null;
5306
            // drag/drop
5307
            var i = jsPlumb.CurrentLibrary.getElementObject(this.canvas);              
5308
            jsPlumb.CurrentLibrary.destroyDraggable(i);
5309
            jsPlumb.CurrentLibrary.destroyDroppable(i);
5310
        },
5311
        setHover : function(h) {
5312
            if (this.endpoint && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged())
5313
                this.endpoint.setHover(h);            
5314
        },
5315
        isFull : function() {
5316
            return !(this.isFloating() || this._jsPlumb.maxConnections < 1 || this.connections.length < this._jsPlumb.maxConnections);              
5317
        },
5318
        getConnectionCost : function() { return this._jsPlumb.connectionCost; },
5319
        setConnectionCost : function(c) {
5320
            this._jsPlumb.connectionCost = c; 
5321
        },
5322
        areConnectionsDirected : function() { return this._jsPlumb.connectionsDirected; },
5323
        setConnectionsDirected : function(b) { this._jsPlumb.connectionsDirected = b; },
5324
        setElementId : function(_elId) {
5325
            this.elementId = _elId;
5326
            this.anchor.elementId = _elId;
5327
        },        
5328
        setReferenceElement : function(_el) {
5329
            this.element = jsPlumb.CurrentLibrary.getDOMElement(_el);
5330
        },
5331
        setDragAllowedWhenFull : function(allowed) {
5332
            this.dragAllowedWhenFull = allowed;
5333
        },
5334
        equals : function(endpoint) {
5335
            return this.anchor.equals(endpoint.anchor);
5336
        },
5337
        getUuid : function() {
5338
            return this._jsPlumb.uuid;
5339
        },
5340
        computeAnchor : function(params) {
5341
            return this.anchor.compute(params);
5342
        }
5343
    });
5344
})();
5345
;(function() {
5346
5347
    var makeConnector = function(_jsPlumb, renderMode, connectorName, connectorArgs) {
5348
            if (!_jsPlumb.Defaults.DoNotThrowErrors && jsPlumb.Connectors[renderMode][connectorName] == null)
5349
                    throw { msg:"jsPlumb: unknown connector type '" + connectorName + "'" };
5350
5351
            return new jsPlumb.Connectors[renderMode][connectorName](connectorArgs);  
5352
        },
5353
        _makeAnchor = function(anchorParams, elementId, _jsPlumb) {
5354
            return (anchorParams) ? _jsPlumb.makeAnchor(anchorParams, elementId, _jsPlumb) : null;
5355
        },
5356
        prepareEndpoint = function(_jsPlumb, _newEndpoint, conn, existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) {
5357
            var e;
5358
            if (existing) {
5359
                conn.endpoints[index] = existing;
5360
                existing.addConnection(conn);                   
5361
            } else {
5362
                if (!params.endpoints) params.endpoints = [ null, null ];
5363
                var ep = params.endpoints[index]  || params.endpoint || _jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoints[index] || _jsPlumb.Defaults.Endpoint || jsPlumb.Defaults.Endpoint;
5364
                if (!params.endpointStyles) params.endpointStyles = [ null, null ];
5365
                if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ];
5366
                var es = params.endpointStyles[index] || params.endpointStyle || _jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _jsPlumb.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
5367
                // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified.
5368
                if (es.fillStyle == null && connectorPaintStyle != null)
5369
                    es.fillStyle = connectorPaintStyle.strokeStyle;
5370
                
5371
                // TODO: decide if the endpoint should derive the connection's outline width and color.  currently it does:
5372
                //*
5373
                if (es.outlineColor == null && connectorPaintStyle != null) 
5374
                    es.outlineColor = connectorPaintStyle.outlineColor;
5375
                if (es.outlineWidth == null && connectorPaintStyle != null) 
5376
                    es.outlineWidth = connectorPaintStyle.outlineWidth;
5377
                //*/
5378
                
5379
                var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _jsPlumb.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _jsPlumb.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle;
5380
                // endpoint hover fill style is derived from connector's hover stroke style.  TODO: do we want to do this by default? for sure?
5381
                if (connectorHoverPaintStyle != null) {
5382
                    if (ehs == null) ehs = {};
5383
                    if (ehs.fillStyle == null) {
5384
                        ehs.fillStyle = connectorHoverPaintStyle.strokeStyle;
5385
                    }
5386
                }
5387
                var a = params.anchors ? params.anchors[index] : 
5388
                        params.anchor ? params.anchor :
5389
                        _makeAnchor(_jsPlumb.Defaults.Anchors[index], elementId, _jsPlumb) || 
5390
                        _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId,_jsPlumb) || 
5391
                        _makeAnchor(_jsPlumb.Defaults.Anchor, elementId,_jsPlumb) || 
5392
                        _makeAnchor(jsPlumb.Defaults.Anchor, elementId, _jsPlumb),                  
5393
                    u = params.uuids ? params.uuids[index] : null;
5394
                    e = _newEndpoint({ 
5395
                        paintStyle : es,  hoverPaintStyle:ehs,  endpoint : ep,  connections : [ conn ], 
5396
                        uuid : u,  anchor : a,  source : element, scope  : params.scope, container:params.container,
5397
                        reattach:params.reattach || _jsPlumb.Defaults.ReattachConnections,
5398
                        detachable:params.detachable || _jsPlumb.Defaults.ConnectionsDetachable
5399
                    });
5400
                conn.endpoints[index] = e;
5401
                
5402
                if (params.drawEndpoints === false) e.setVisible(false, true, true);
5403
                                    
5404
            }
5405
            return e;
5406
        };
5407
    
5408
    jsPlumb.Connection = function(params) {
5409
        var _newConnection = params.newConnection,
5410
            _newEndpoint = params.newEndpoint,
5411
            jpcl = jsPlumb.CurrentLibrary,
5412
            _att = jpcl.getAttribute,
5413
            _gel = jpcl.getElementObject,
5414
            _dom = jpcl.getDOMElement,
5415
            _ju = jsPlumbUtil,
5416
            _getOffset = jpcl.getOffset;
5417
5418
        this.connector = null;                        
5419
        this.idPrefix = "_jsplumb_c_";
5420
        this.defaultLabelLocation = 0.5;
5421
        this.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"];
5422
        this.parent = params.parent;
5423
        // if a new connection is the result of moving some existing connection, params.previousConnection
5424
        // will have that Connection in it. listeners for the jsPlumbConnection event can look for that
5425
        // member and take action if they need to.
5426
        this.previousConnection = params.previousConnection;
5427
        this.source = _dom(params.source);
5428
        this.target = _dom(params.target);
5429
        // sourceEndpoint and targetEndpoint override source/target, if they are present. but 
5430
        // source is not overridden if the Endpoint has declared it is not the final target of a connection;
5431
        // instead we use the source that the Endpoint declares will be the final source element.
5432
        if (params.sourceEndpoint) this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement();            
5433
        if (params.targetEndpoint) this.target = params.targetEndpoint.getElement();        
5434
5435
        OverlayCapableJsPlumbUIComponent.apply(this, arguments);
5436
5437
        this.sourceId = this._jsPlumb.instance.getId(this.source);
5438
        this.targetId = this._jsPlumb.instance.getId(this.target);
5439
        this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints.            
5440
        this.endpoints = [];
5441
        this.endpointStyles = [];
5442
            
5443
        var _jsPlumb = this._jsPlumb.instance;    
5444
        this._jsPlumb.visible = true;
5445
        this._jsPlumb.editable = params.editable === true;    
5446
        this._jsPlumb.params = {
5447
            parent:params.parent,
5448
            cssClass:params.cssClass,
5449
            container:params.container,
5450
            "pointer-events":params["pointer-events"],
5451
            editorParams:params.editorParams
5452
        };   
5453
        this._jsPlumb.lastPaintedAt = null;              
5454
        this.getDefaultType = function() {
5455
            return {
5456
                parameters:{},
5457
                scope:null,
5458
                detachable:this._jsPlumb.instance.Defaults.ConnectionsDetachable,
5459
                rettach:this._jsPlumb.instance.Defaults.ReattachConnections,
5460
                paintStyle:this._jsPlumb.instance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle,
5461
                connector:this._jsPlumb.instance.Defaults.Connector || jsPlumb.Defaults.Connector,
5462
                hoverPaintStyle:this._jsPlumb.instance.Defaults.HoverPaintStyle || jsPlumb.Defaults.HoverPaintStyle,				
5463
                overlays:this._jsPlumb.instance.Defaults.ConnectorOverlays || jsPlumb.Defaults.ConnectorOverlays
5464
            };
5465
        };
5466
        
5467
// INITIALISATION CODE			
5468
                            
5469
        // wrapped the main function to return null if no input given. this lets us cascade defaults properly.
5470
        
5471
        var eS = prepareEndpoint(_jsPlumb, _newEndpoint, this, params.sourceEndpoint, 0, params, this.source, this.sourceId, params.paintStyle, params.hoverPaintStyle);			
5472
        if (eS) _ju.addToList(params.endpointsByElement, this.sourceId, eS);						
5473
        var eT = prepareEndpoint(_jsPlumb, _newEndpoint, this, params.targetEndpoint, 1, params, this.target, this.targetId, params.paintStyle, params.hoverPaintStyle);
5474
        if (eT) _ju.addToList(params.endpointsByElement, this.targetId, eT);
5475
        // if scope not set, set it to be the scope for the source endpoint.
5476
        if (!this.scope) this.scope = this.endpoints[0].scope;		
5477
                
5478
        // if explicitly told to (or not to) delete endpoints on detach, override endpoint's preferences
5479
        if (params.deleteEndpointsOnDetach != null) {
5480
            this.endpoints[0]._deleteOnDetach = params.deleteEndpointsOnDetach;
5481
            this.endpoints[1]._deleteOnDetach = params.deleteEndpointsOnDetach;
5482
        }
5483
        else {
5484
            // otherwise, unless the endpoints say otherwise, mark them for deletion.
5485
            if (!this.endpoints[0]._doNotDeleteOnDetach) this.endpoints[0]._deleteOnDetach = true;
5486
            if (!this.endpoints[1]._doNotDeleteOnDetach) this.endpoints[1]._deleteOnDetach = true;
5487
        }   
5488
                    
5489
        // TODO these could surely be refactored into some method that tries them one at a time until something exists
5490
        this.setConnector(this.endpoints[0].connector || 
5491
                          this.endpoints[1].connector || 
5492
                          params.connector || 
5493
                          _jsPlumb.Defaults.Connector || 
5494
                          jsPlumb.Defaults.Connector, true);
5495
5496
        if (params.path)
5497
            this.connector.setPath(params.path);
5498
        
5499
        this.setPaintStyle(this.endpoints[0].connectorStyle || 
5500
                           this.endpoints[1].connectorStyle || 
5501
                           params.paintStyle || 
5502
                           _jsPlumb.Defaults.PaintStyle || 
5503
                           jsPlumb.Defaults.PaintStyle, true);
5504
                    
5505
        this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle || 
5506
                                this.endpoints[1].connectorHoverStyle || 
5507
                                params.hoverPaintStyle || 
5508
                                _jsPlumb.Defaults.HoverPaintStyle || 
5509
                                jsPlumb.Defaults.HoverPaintStyle, true);
5510
        
5511
        this._jsPlumb.paintStyleInUse = this.getPaintStyle();
5512
        
5513
        var _suspendedAt = _jsPlumb.getSuspendedAt();
5514
        _jsPlumb.updateOffset( { elId : this.sourceId, timestamp:_suspendedAt });
5515
        _jsPlumb.updateOffset( { elId : this.targetId, timestamp:_suspendedAt });
5516
5517
//*
5518
        if(!_jsPlumb.isSuspendDrawing()) {                    
5519
            // paint the endpoints
5520
            var myInfo = _jsPlumb.getCachedData(this.sourceId),
5521
                myOffset = myInfo.o, myWH = myInfo.s,
5522
                otherInfo = _jsPlumb.getCachedData(this.targetId),
5523
                otherOffset = otherInfo.o,
5524
                otherWH = otherInfo.s,
5525
                initialTimestamp = _suspendedAt || _jsPlumb.timestamp(),
5526
                anchorLoc = this.endpoints[0].anchor.compute( {
5527
                    xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0],
5528
                    elementId:this.endpoints[0].elementId,
5529
                    txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1],
5530
                    timestamp:initialTimestamp
5531
                });
5532
5533
            this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp });
5534
5535
            anchorLoc = this.endpoints[1].anchor.compute( {
5536
                xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1],
5537
                elementId:this.endpoints[1].elementId,				
5538
                txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0],
5539
                timestamp:initialTimestamp				
5540
            });
5541
            this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp });
5542
        }
5543
        //*/
5544
                                
5545
// END INITIALISATION CODE			
5546
        
5547
// DETACHABLE 				
5548
        this._jsPlumb.detachable = _jsPlumb.Defaults.ConnectionsDetachable;
5549
        if (params.detachable === false) this._jsPlumb.detachable = false;
5550
        if(this.endpoints[0].connectionsDetachable === false) this._jsPlumb.detachable = false;
5551
        if(this.endpoints[1].connectionsDetachable === false) this._jsPlumb.detachable = false;                
5552
// REATTACH
5553
        this._jsPlumb.reattach = params.reattach || this.endpoints[0].reattachConnections || this.endpoints[1].reattachConnections || _jsPlumb.Defaults.ReattachConnections;
5554
// COST + DIRECTIONALITY
5555
        // if cost not supplied, try to inherit from source endpoint
5556
        this._jsPlumb.cost = params.cost || this.endpoints[0].getConnectionCost();			        
5557
        this._jsPlumb.directed = params.directed;
5558
        // inherit directed flag if set no source endpoint
5559
        if (params.directed == null) this._jsPlumb.directed = this.endpoints[0].areConnectionsDirected();        
5560
// END COST + DIRECTIONALITY
5561
                    
5562
// PARAMETERS						
5563
        // merge all the parameters objects into the connection.  parameters set
5564
        // on the connection take precedence; then source endpoint params, then
5565
        // finally target endpoint params.
5566
        // TODO jsPlumb.extend could be made to take more than two args, and it would
5567
        // apply the second through nth args in order.
5568
        var _p = jsPlumb.extend({}, this.endpoints[1].getParameters());
5569
        jsPlumb.extend(_p, this.endpoints[0].getParameters());
5570
        jsPlumb.extend(_p, this.getParameters());
5571
        this.setParameters(_p);
5572
// END PARAMETERS
5573
5574
// PAINTING
5575
                  
5576
        // the very last thing we do is apply types, if there are any.
5577
        var _types = [params.type, this.endpoints[0].connectionType, this.endpoints[1].connectionType ].join(" ");
5578
        if (/[a-zA-Z]/.test(_types))
5579
            this.addType(_types, params.data, true);        
5580
5581
        
5582
// END PAINTING    
5583
    };
5584
5585
    jsPlumbUtil.extend(jsPlumb.Connection, OverlayCapableJsPlumbUIComponent, {
5586
        applyType : function(t, doNotRepaint) {            
5587
            if (t.detachable != null) this.setDetachable(t.detachable);
5588
            if (t.reattach != null) this.setReattach(t.reattach);
5589
            if (t.scope) this.scope = t.scope;
5590
            //editable = t.editable;  // TODO
5591
            this.setConnector(t.connector, doNotRepaint);
5592
        },
5593
        getTypeDescriptor : function() { return "connection"; },
5594
        getAttachedElements : function() {
5595
            return this.endpoints;
5596
        },
5597
        addClass : function(c, informEndpoints) {        
5598
            if (informEndpoints) {
5599
                this.endpoints[0].addClass(c);
5600
                this.endpoints[1].addClass(c); 
5601
                if (this.suspendedEndpoint) this.suspendedEndpoint.addClass(c);                   
5602
            }
5603
            if (this.connector) {
5604
                this.connector.addClass(c);
5605
            }
5606
        },
5607
        removeClass : function(c, informEndpoints) {            
5608
            if (informEndpoints) {
5609
                this.endpoints[0].removeClass(c);
5610
                this.endpoints[1].removeClass(c);                    
5611
                if (this.suspendedEndpoint) this.suspendedEndpoint.removeClass(c);
5612
            }
5613
            if (this.connector) {
5614
                this.connector.removeClass(c);
5615
            }
5616
        },
5617
        isVisible : function() { return this._jsPlumb.visible; },
5618
        setVisible : function(v) {
5619
            this._jsPlumb.visible = v;
5620
            //this[v ? "showOverlays" : "hideOverlays"]();
5621
            if (this.connector) 
5622
                this.connector.setVisible(v);
5623
            this.repaint();
5624
        },
5625
5626
        /* TODO move to connecto editors; it should put these on the prototype.
5627
5628
        setEditable : function(e) {
5629
            if (this.connector && this.connector.isEditable())
5630
                this._jsPlumb.editable = e;
5631
            
5632
            return this._jsPlumb.editable;
5633
        },
5634
        isEditable : function() { return this._jsPlumb.editable; },
5635
        editStarted : function() {  
5636
            this.setSuspendEvents(true);
5637
            this.fire("editStarted", {
5638
                path:this.connector.getPath()
5639
            });            
5640
            this._jsPlumb.instance.setHoverSuspended(true);
5641
        },
5642
        editCompleted : function() {            
5643
            this.fire("editCompleted", {
5644
                path:this.connector.getPath()
5645
            });       
5646
            this.setSuspendEvents(false);
5647
            this.setHover(false);     
5648
            this._jsPlumb.instance.setHoverSuspended(false);
5649
        },
5650
        editCanceled : function() {
5651
            this.fire("editCanceled", {
5652
                path:this.connector.getPath()
5653
            });
5654
            this.setHover(false);
5655
            this._jsPlumb.instance.setHoverSuspended(false);
5656
        },
5657
5658
*/
5659
5660
        cleanup:function() {
5661
            //this.endpointsToDeleteOnDetach = null;
5662
            this.endpoints = null;
5663
            this.source = null;
5664
            this.target = null;                    
5665
            if (this.connector != null) {
5666
                this.connector.cleanup();            
5667
                this.connector.destroy();
5668
            }
5669
            this.connector = null;
5670
        },
5671
        isDetachable : function() {
5672
            return this._jsPlumb.detachable === true;
5673
        },
5674
        setDetachable : function(detachable) {
5675
          this._jsPlumb.detachable = detachable === true;
5676
        },
5677
        isReattach : function() {
5678
            return this._jsPlumb.reattach === true;
5679
        },        
5680
        setReattach : function(reattach) {
5681
          this._jsPlumb.reattach = reattach === true;
5682
        },
5683
        setHover : function(state) {
5684
            if (this.connector && this._jsPlumb && !this._jsPlumb.instance.isConnectionBeingDragged()) {
5685
                this.connector.setHover(state);
5686
                jsPlumb.CurrentLibrary[state ? "addClass" : "removeClass"](this.source, this._jsPlumb.instance.hoverSourceClass);
5687
                jsPlumb.CurrentLibrary[state ? "addClass" : "removeClass"](this.target, this._jsPlumb.instance.hoverTargetClass);
5688
            }
5689
        },
5690
        getCost : function() { return this._jsPlumb.cost; },
5691
        setCost : function(c) { this._jsPlumb.cost = c; },
5692
        isDirected : function() { return this._jsPlumb.directed === true; },
5693
        //
5694
        // changes the parent element of this connection to newParent.  not exposed for the public API.
5695
        //
5696
        // TODO ensure moveParent method still works (the overlay stuff in particular)
5697
        moveParent : function(newParent) {
5698
            var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(this.connector.canvas);               
5699
            if (this.connector.bgCanvas) {
5700
                jpcl.removeElement(this.connector.bgCanvas);
5701
                jpcl.appendElement(this.connector.bgCanvas, newParent);
5702
            }
5703
            jpcl.removeElement(this.connector.canvas);
5704
            jpcl.appendElement(this.connector.canvas, newParent);                
5705
            // this only applies for DOMOverlays
5706
            for (var i = 0; i < this._jsPlumb.overlays.length; i++) {
5707
                if (this._jsPlumb.overlays[i].isAppendedAtTopLevel) {
5708
                    jpcl.removeElement(this._jsPlumb.overlays[i].canvas);
5709
                    jpcl.appendElement(this._jsPlumb.overlays[i].canvas, newParent);
5710
                    if (this._jsPlumb.overlays[i].reattachListeners) 
5711
                        this._jsPlumb.overlays[i].reattachListeners(this.connector);
5712
                }
5713
            }
5714
            if (this.connector.reattachListeners)       // this is for SVG/VML; change an element's parent and you have to reinit its listeners.
5715
                this.connector.reattachListeners();     // the Canvas implementation doesn't have to care about this
5716
        },
5717
        getConnector : function() { return this.connector; },
5718
        setConnector : function(connectorSpec, doNotRepaint) {
5719
            var _ju = jsPlumbUtil;
5720
            if (this.connector != null) {
5721
                this.connector.cleanup();
5722
                this.connector.destroy();
5723
            }
5724
5725
            var connectorArgs = { 
5726
                    _jsPlumb:this._jsPlumb.instance, 
5727
                    parent:this._jsPlumb.params.parent, 
5728
                    cssClass:this._jsPlumb.params.cssClass, 
5729
                    container:this._jsPlumb.params.container,                 
5730
                    "pointer-events":this._jsPlumb.params["pointer-events"]
5731
                },
5732
                renderMode = this._jsPlumb.instance.getRenderMode();
5733
            
5734
            if (_ju.isString(connectorSpec)) 
5735
                this.connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec, connectorArgs); // lets you use a string as shorthand.
5736
            else if (_ju.isArray(connectorSpec)) {
5737
                if (connectorSpec.length == 1)
5738
                    this.connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec[0], connectorArgs);
5739
                else
5740
                    this.connector = makeConnector(this._jsPlumb.instance, renderMode, connectorSpec[0], _ju.merge(connectorSpec[1], connectorArgs));
5741
            }
5742
            // binds mouse listeners to the current connector.
5743
            this.bindListeners(this.connector, this, function(state) {                
5744
                this.setHover(state, false);                
5745
            }.bind(this));
5746
            
5747
            this.canvas = this.connector.canvas;
5748
5749
            if (this._jsPlumb.editable && jsPlumb.ConnectorEditors != null && jsPlumb.ConnectorEditors[this.connector.type] && this.connector.isEditable()) {
5750
                new jsPlumb.ConnectorEditors[this.connector.type]({
5751
                    connector:this.connector,
5752
                    connection:this,
5753
                    params:this._jsPlumb.params.editorParams || { }
5754
                });
5755
            }
5756
            else {                    
5757
                this._jsPlumb.editable = false;
5758
            }                
5759
                
5760
            if (!doNotRepaint) this.repaint();
5761
        },
5762
        paint : function(params) {
5763
                    
5764
            if (!this._jsPlumb.instance.isSuspendDrawing() && this._jsPlumb.visible) {
5765
                    
5766
                params = params || {};
5767
                var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp,
5768
                    // if the moving object is not the source we must transpose the two references.
5769
                    swap = false,
5770
                    tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId,                    
5771
                    tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
5772
5773
                if (timestamp == null || timestamp != this._jsPlumb.lastPaintedAt) {                        
5774
                    var sourceInfo = this._jsPlumb.instance.updateOffset( { elId : sId, offset : ui, recalc : recalc, timestamp : timestamp }).o,
5775
                        targetInfo = this._jsPlumb.instance.updateOffset( { elId : tId, timestamp : timestamp }).o, // update the target if this is a forced repaint. otherwise, only the source has been moved.
5776
                        sE = this.endpoints[sIdx], tE = this.endpoints[tIdx];
5777
5778
                    if (params.clearEdits) {
5779
                        this._jsPlumb.overlayPositions = null;
5780
                        sE.anchor.clearUserDefinedLocation();
5781
                        tE.anchor.clearUserDefinedLocation();
5782
                        this.connector.setEdited(false);
5783
                    }
5784
                    
5785
                    var sAnchorP = sE.anchor.getCurrentLocation({xy:[sourceInfo.left,sourceInfo.top], wh:[sourceInfo.width, sourceInfo.height], element:sE, timestamp:timestamp}),              
5786
                        tAnchorP = tE.anchor.getCurrentLocation({xy:[targetInfo.left,targetInfo.top], wh:[targetInfo.width, targetInfo.height], element:tE, timestamp:timestamp});                                                 
5787
                        
5788
                    this.connector.resetBounds();
5789
5790
                    this.connector.compute({
5791
                        sourcePos:sAnchorP,
5792
                        targetPos:tAnchorP, 
5793
                        sourceEndpoint:this.endpoints[sIdx],
5794
                        targetEndpoint:this.endpoints[tIdx],
5795
                        lineWidth:this._jsPlumb.paintStyleInUse.lineWidth,                                          
5796
                        sourceInfo:sourceInfo,
5797
                        targetInfo:targetInfo,
5798
                        clearEdits:params.clearEdits === true
5799
                    });                                                                                        
5800
5801
                    var overlayExtents = { minX:Infinity, minY:Infinity, maxX:-Infinity, maxY:-Infinity };
5802
                                        
5803
                    // compute overlays. we do this first so we can get their placements, and adjust the
5804
                    // container if needs be (if an overlay would be clipped)
5805
                    for ( var i = 0; i < this._jsPlumb.overlays.length; i++) {
5806
                        var o = this._jsPlumb.overlays[i];
5807
                        if (o.isVisible()) {                            
5808
                            this._jsPlumb.overlayPlacements[i] = o.draw(this.connector, this._jsPlumb.paintStyleInUse, this.getAbsoluteOverlayPosition(o));
5809
                            overlayExtents.minX = Math.min(overlayExtents.minX, this._jsPlumb.overlayPlacements[i].minX);
5810
                            overlayExtents.maxX = Math.max(overlayExtents.maxX, this._jsPlumb.overlayPlacements[i].maxX);
5811
                            overlayExtents.minY = Math.min(overlayExtents.minY, this._jsPlumb.overlayPlacements[i].minY);
5812
                            overlayExtents.maxY = Math.max(overlayExtents.maxY, this._jsPlumb.overlayPlacements[i].maxY);
5813
                        }
5814
                    }
5815
5816
                    var lineWidth = parseFloat(this._jsPlumb.paintStyleInUse.lineWidth || 1) / 2,
5817
                        outlineWidth = parseFloat(this._jsPlumb.paintStyleInUse.lineWidth || 0),
5818
                        extents = {
5819
                            xmin : Math.min(this.connector.bounds.minX - (lineWidth + outlineWidth), overlayExtents.minX),
5820
                            ymin : Math.min(this.connector.bounds.minY - (lineWidth + outlineWidth), overlayExtents.minY),
5821
                            xmax : Math.max(this.connector.bounds.maxX + (lineWidth + outlineWidth), overlayExtents.maxX),
5822
                            ymax : Math.max(this.connector.bounds.maxY + (lineWidth + outlineWidth), overlayExtents.maxY)
5823
                        };
5824
5825
                    // paint the connector.
5826
                    this.connector.paint(this._jsPlumb.paintStyleInUse, null, extents);  
5827
                    // and then the overlays
5828
                    for ( var j = 0; j < this._jsPlumb.overlays.length; j++) {
5829
                        var p = this._jsPlumb.overlays[j];
5830
                        if (p.isVisible()) {
5831
                            p.paint(this._jsPlumb.overlayPlacements[j], extents);    
5832
                        }
5833
                    }                                                                          
5834
                }
5835
                this._jsPlumb.lastPaintedAt = timestamp;                        
5836
            }       
5837
        },
5838
        /*
5839
         * Function: repaint
5840
         * Repaints the Connection. No parameters exposed to public API.
5841
         */
5842
        repaint : function(params) {
5843
            params = params || {};            
5844
            this.paint({ elId : this.sourceId, recalc : !(params.recalc === false), timestamp:params.timestamp, clearEdits:params.clearEdits });
5845
        }
5846
        
5847
    }); // END Connection class            
5848
})();
5849
/*
5850
 * jsPlumb
5851
 * 
5852
 * Title:jsPlumb 1.5.5
5853
 * 
5854
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
5855
 * elements, or VML.  
5856
 * 
5857
 * This file contains the code for creating and manipulating anchors.
5858
 *
5859
 * Copyright (c) 2010 - 2013 Simon Porritt ([email protected])
5860
 * 
5861
 * http://jsplumb.org
5862
 * http://github.com/sporritt/jsplumb
5863
 * http://code.google.com/p/jsplumb
5864
 * 
5865
 * Dual licensed under the MIT and GPL2 licenses.
5866
 */
5867
;(function() {	
5868
    
5869
    //
5870
	// manages anchors for all elements.
5871
	//
5872
	jsPlumb.AnchorManager = function(params) {
5873
		var _amEndpoints = {},
5874
            continuousAnchors = {},
5875
            continuousAnchorLocations = {},
5876
            userDefinedContinuousAnchorLocations = {},        
5877
            continuousAnchorOrientations = {},
5878
            Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" },
5879
			connectionsByElementId = {},
5880
			self = this,
5881
            anchorLists = {},
5882
            jsPlumbInstance = params.jsPlumbInstance,
5883
            jpcl = jsPlumb.CurrentLibrary,
5884
            floatingConnections = {},
5885
            // TODO this functions uses a crude method of determining orientation between two elements.
5886
            // 'diagonal' should be chosen when the angle of the line between the two centers is around
5887
            // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees.
5888
            // used by AnchorManager.redraw
5889
            calculateOrientation = function(sourceId, targetId, sd, td, sourceAnchor, targetAnchor) {
5890
        
5891
                if (sourceId === targetId) return {
5892
                    orientation:Orientation.IDENTITY,
5893
                    a:["top", "top"]
5894
                };
5895
        
5896
                var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)),
5897
                    theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)),
5898
                    h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) ||
5899
                        (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)),
5900
                    v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) ||
5901
                        (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom)),
5902
                    possiblyTranslateEdges = function(edges) {
5903
                        // this function checks to see if either anchor is Continuous, and if so, runs the suggested edge
5904
                        // through the anchor: Continuous anchors can say which faces they support, and they get to choose 
5905
                        // whether a certain face is honoured, or, if not, which face to replace it with. the behaviour when
5906
                        // choosing an alternate face is to try for the opposite face first, then the next one clockwise, and then
5907
                        // the opposite of that one.
5908
                        return [
5909
                            sourceAnchor.isContinuous ? sourceAnchor.verifyEdge(edges[0]) : edges[0],    
5910
                            targetAnchor.isContinuous ? targetAnchor.verifyEdge(edges[1]) : edges[1]
5911
                        ];
5912
                    },
5913
                    out = {
5914
                        orientation:Orientation.DIAGONAL,
5915
                        theta:theta,
5916
                        theta2:theta2
5917
                    };                        
5918
                
5919
                if (! (h || v)) {                    
5920
                    if (td.left > sd.left && td.top > sd.top)
5921
                        out.a = ["right", "top"];
5922
                    else if (td.left > sd.left && sd.top > td.top)
5923
                        out.a = [ "top", "left"];
5924
                    else if (td.left < sd.left && td.top < sd.top)
5925
                        out.a = [ "top", "right"];
5926
                    else if (td.left < sd.left && td.top > sd.top)
5927
                        out.a = ["left", "top" ];                            
5928
                }
5929
                else if (h) {
5930
                    out.orientation = Orientation.HORIZONTAL;
5931
                    out.a = sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"];                    
5932
                }
5933
                else {
5934
                    out.orientation = Orientation.VERTICAL;
5935
                    out.a = sd.left < td.left ? ["right", "left"] : ["left", "right"];
5936
                }
5937
                
5938
                out.a = possiblyTranslateEdges(out.a);
5939
                return out;
5940
            },
5941
                // used by placeAnchors function
5942
            placeAnchorsOnLine = function(desc, elementDimensions, elementPosition,
5943
                            connections, horizontal, otherMultiplier, reverse) {
5944
                var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1);
5945
        
5946
                for (var i = 0; i < connections.length; i++) {
5947
                    var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0];
5948
                    if (reverse)
5949
                      val = elementDimensions[horizontal ? 0 : 1] - val;
5950
        
5951
                    var dx = (horizontal ? val : other), x = elementPosition[0] + dx,  xp = dx / elementDimensions[0],
5952
                        dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1];
5953
        
5954
                    a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]);
5955
                }
5956
        
5957
                return a;
5958
            },
5959
            // used by edgeSortFunctions        
5960
            currySort = function(reverseAngles) {
5961
                return function(a,b) {
5962
                    var r = true;
5963
                    if (reverseAngles) {
5964
                        /*if (a[0][0] < b[0][0])
5965
                            r = true;
5966
                        else
5967
                            r = a[0][1] > b[0][1];*/
5968
                        r = a[0][0] < b[0][0];
5969
                    }
5970
                    else {
5971
                        /*if (a[0][0] > b[0][0])
5972
                            r= true;
5973
                        else
5974
                            r =a[0][1] > b[0][1];
5975
                        */
5976
                        r = a[0][0] > b[0][0];
5977
                    }
5978
                    return r === false ? -1 : 1;
5979
                };
5980
            },
5981
                // used by edgeSortFunctions
5982
            leftSort = function(a,b) {
5983
                // first get adjusted values
5984
                var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0],
5985
                p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0];
5986
                if (p1 > p2) return 1;
5987
                else return a[0][1] > b[0][1] ? 1 : -1;
5988
            },
5989
                // used by placeAnchors
5990
            edgeSortFunctions = {
5991
                "top":function(a, b) { return a[0] > b[0] ? 1 : -1; },
5992
                "right":currySort(true),
5993
                "bottom":currySort(true),
5994
                "left":leftSort
5995
            },
5996
                // used by placeAnchors
5997
            _sortHelper = function(_array, _fn) { return _array.sort(_fn); },
5998
                // used by AnchorManager.redraw
5999
            placeAnchors = function(elementId, _anchorLists) {		
6000
                var cd = jsPlumbInstance.getCachedData(elementId), sS = cd.s, sO = cd.o,
6001
                placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) {
6002
                    if (unsortedConnections.length > 0) {
6003
                        var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen
6004
                            reverse = desc === "right" || desc === "top",
6005
                            anchors = placeAnchorsOnLine(desc, elementDimensions,
6006
                                                     elementPosition, sc,
6007
                                                     isHorizontal, otherMultiplier, reverse );
6008
        
6009
                        // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it.
6010
                        var _setAnchorLocation = function(endpoint, anchorPos) {
6011
                            var a = jsPlumbInstance.adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas);
6012
                            continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ];
6013
                            continuousAnchorOrientations[endpoint.id] = orientation;
6014
                        };
6015
        
6016
                        for (var i = 0; i < anchors.length; i++) {
6017
                            var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId;
6018
                            if (weAreSource)
6019
                                _setAnchorLocation(c.endpoints[0], anchors[i]);
6020
                            else if (weAreTarget)
6021
                                _setAnchorLocation(c.endpoints[1], anchors[i]);
6022
                        }
6023
                    }
6024
                };
6025
        
6026
                placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]);
6027
                placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]);
6028
                placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]);
6029
                placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]);
6030
            };
6031
6032
        this.reset = function() {
6033
            _amEndpoints = {};
6034
            connectionsByElementId = {};
6035
            anchorLists = {};
6036
        };			
6037
        this.addFloatingConnection = function(key, conn) {
6038
            floatingConnections[key] = conn;
6039
        };
6040
        this.removeFloatingConnection = function(key) {
6041
            delete floatingConnections[key];
6042
        };                                                 
6043
        this.newConnection = function(conn) {
6044
			var sourceId = conn.sourceId, targetId = conn.targetId,
6045
				ep = conn.endpoints,
6046
                doRegisterTarget = true,
6047
                registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
6048
					if ((sourceId == targetId) && otherAnchor.isContinuous){
6049
                       // remove the target endpoint's canvas.  we dont need it.
6050
                        jpcl.removeElement(ep[1].canvas);
6051
                        doRegisterTarget = false;
6052
                    }
6053
					jsPlumbUtil.addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == jsPlumb.DynamicAnchor]);
6054
			    };
6055
6056
			registerConnection(0, ep[0], ep[0].anchor, targetId, conn);
6057
            if (doRegisterTarget)
6058
            	registerConnection(1, ep[1], ep[1].anchor, sourceId, conn);
6059
		};
6060
        var removeEndpointFromAnchorLists = function(endpoint) {
6061
            (function(list, eId) {
6062
                if (list) {  // transient anchors dont get entries in this list.
6063
                    var f = function(e) { return e[4] == eId; };
6064
                    jsPlumbUtil.removeWithFunction(list.top, f);
6065
                    jsPlumbUtil.removeWithFunction(list.left, f);
6066
                    jsPlumbUtil.removeWithFunction(list.bottom, f);
6067
                    jsPlumbUtil.removeWithFunction(list.right, f);
6068
                }
6069
            })(anchorLists[endpoint.elementId], endpoint.id);
6070
        };
6071
		this.connectionDetached = function(connInfo) {
6072
            var connection = connInfo.connection || connInfo,
6073
			    sourceId = connInfo.sourceId,
6074
                targetId = connInfo.targetId,
6075
				ep = connection.endpoints,
6076
				removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
6077
					if (otherAnchor != null && otherAnchor.constructor == jsPlumb.FloatingAnchor) {
6078
						// no-op
6079
					}
6080
					else {
6081
						jsPlumbUtil.removeWithFunction(connectionsByElementId[elId], function(_c) {
6082
							return _c[0].id == c.id;
6083
						});
6084
					}
6085
				};
6086
				
6087
			removeConnection(1, ep[1], ep[1].anchor, sourceId, connection);
6088
			removeConnection(0, ep[0], ep[0].anchor, targetId, connection);
6089
6090
            // remove from anchorLists            
6091
            removeEndpointFromAnchorLists(connection.endpoints[0]);
6092
            removeEndpointFromAnchorLists(connection.endpoints[1]);
6093
6094
            self.redraw(connection.sourceId);
6095
            self.redraw(connection.targetId);
6096
		};
6097
		this.add = function(endpoint, elementId) {
6098
			jsPlumbUtil.addToList(_amEndpoints, elementId, endpoint);
6099
		};
6100
		this.changeId = function(oldId, newId) {
6101
			connectionsByElementId[newId] = connectionsByElementId[oldId];
6102
			_amEndpoints[newId] = _amEndpoints[oldId];
6103
			delete connectionsByElementId[oldId];
6104
			delete _amEndpoints[oldId];	
6105
		};
6106
		this.getConnectionsFor = function(elementId) {
6107
			return connectionsByElementId[elementId] || [];
6108
		};
6109
		this.getEndpointsFor = function(elementId) {
6110
			return _amEndpoints[elementId] || [];
6111
		};
6112
		this.deleteEndpoint = function(endpoint) {
6113
			jsPlumbUtil.removeWithFunction(_amEndpoints[endpoint.elementId], function(e) {
6114
				return e.id == endpoint.id;
6115
			});
6116
            removeEndpointFromAnchorLists(endpoint);
6117
		};
6118
		this.clearFor = function(elementId) {
6119
			delete _amEndpoints[elementId];
6120
			_amEndpoints[elementId] = [];
6121
		};
6122
        // updates the given anchor list by either updating an existing anchor's info, or adding it. this function
6123
        // also removes the anchor from its previous list, if the edge it is on has changed.
6124
        // all connections found along the way (those that are connected to one of the faces this function
6125
        // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint
6126
        // them wthout having to calculate anything else about them.
6127
        var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) {        
6128
            // first try to find the exact match, but keep track of the first index of a matching element id along the way.s
6129
            var exactIdx = -1,
6130
                firstMatchingElIdx = -1,
6131
                endpoint = conn.endpoints[idx],
6132
                endpointId = endpoint.id,
6133
                oIdx = [1,0][idx],
6134
                values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ],
6135
                listToAddTo = lists[edgeId],
6136
                listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null;
6137
6138
            if (listToRemoveFrom) {
6139
                var rIdx = jsPlumbUtil.findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId; });
6140
                if (rIdx != -1) {
6141
                    listToRemoveFrom.splice(rIdx, 1);
6142
                    // get all connections from this list
6143
                    for (var i = 0; i < listToRemoveFrom.length; i++) {
6144
                        jsPlumbUtil.addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id; });
6145
                        jsPlumbUtil.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id; });
6146
                        jsPlumbUtil.addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[oIdx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[oIdx].id; });
6147
                    }
6148
                }
6149
            }
6150
6151
            for (i = 0; i < listToAddTo.length; i++) {
6152
                if (params.idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1)
6153
                    firstMatchingElIdx = i;
6154
                jsPlumbUtil.addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id; });                
6155
                jsPlumbUtil.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id; });
6156
                jsPlumbUtil.addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[oIdx], function(e) { return e.id == listToAddTo[i][1].endpoints[oIdx].id; });
6157
            }
6158
            if (exactIdx != -1) {
6159
                listToAddTo[exactIdx] = values;
6160
            }
6161
            else {
6162
                var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly.
6163
                listToAddTo.splice(insertIdx, 0, values);
6164
            }
6165
6166
            // store this for next time.
6167
            endpoint._continuousAnchorEdge = edgeId;
6168
        };
6169
6170
        //
6171
        // find the entry in an endpoint's list for this connection and update its target endpoint
6172
        // with the current target in the connection.
6173
        // 
6174
        //
6175
        this.updateOtherEndpoint = function(elId, oldTargetId, newTargetId, connection) {
6176
            var sIndex = jsPlumbUtil.findWithFunction(connectionsByElementId[elId], function(i) {
6177
                    return i[0].id === connection.id;
6178
                }),
6179
                tIndex = jsPlumbUtil.findWithFunction(connectionsByElementId[oldTargetId], function(i) {
6180
                    return i[0].id === connection.id;
6181
                });
6182
6183
            // update or add data for source
6184
            if (sIndex != -1) {
6185
                connectionsByElementId[elId][sIndex][0] = connection;
6186
                connectionsByElementId[elId][sIndex][1] = connection.endpoints[1];
6187
                connectionsByElementId[elId][sIndex][2] = connection.endpoints[1].anchor.constructor == jsPlumb.DynamicAnchor;
6188
            }
6189
6190
            // remove entry for previous target (if there)
6191
            if (tIndex > -1) {
6192
6193
                connectionsByElementId[oldTargetId].splice(tIndex, 1);
6194
                // add entry for new target
6195
                jsPlumbUtil.addToList(connectionsByElementId, newTargetId, [connection, connection.endpoints[0], connection.endpoints[0].anchor.constructor == jsPlumb.DynamicAnchor]);         
6196
            }
6197
        };       
6198
        
6199
        //
6200
        // notification that the connection given has changed source from the originalId to the newId.
6201
        // This involves:
6202
        // 1. removing the connection from the list of connections stored for the originalId
6203
        // 2. updating the source information for the target of the connection
6204
        // 3. re-registering the connection in connectionsByElementId with the newId
6205
        //
6206
        this.sourceChanged = function(originalId, newId, connection) {            
6207
            // remove the entry that points from the old source to the target
6208
            jsPlumbUtil.removeWithFunction(connectionsByElementId[originalId], function(info) {
6209
                return info[0].id === connection.id;
6210
            });
6211
            // find entry for target and update it
6212
            var tIdx = jsPlumbUtil.findWithFunction(connectionsByElementId[connection.targetId], function(i) {
6213
                return i[0].id === connection.id;
6214
            });
6215
            if (tIdx > -1) {
6216
                connectionsByElementId[connection.targetId][tIdx][0] = connection;
6217
                connectionsByElementId[connection.targetId][tIdx][1] = connection.endpoints[0];
6218
                connectionsByElementId[connection.targetId][tIdx][2] = connection.endpoints[0].anchor.constructor == jsPlumb.DynamicAnchor;
6219
            }
6220
            // add entry for new source
6221
            jsPlumbUtil.addToList(connectionsByElementId, newId, [connection, connection.endpoints[1], connection.endpoints[1].anchor.constructor == jsPlumb.DynamicAnchor]);         
6222
        };
6223
6224
        //
6225
        // moves the given endpoint from `currentId` to `element`.
6226
        // This involves:
6227
        //
6228
        // 1. changing the key in _amEndpoints under which the endpoint is stored
6229
        // 2. changing the source or target values in all of the endpoint's connections
6230
        // 3. changing the array in connectionsByElementId in which the endpoint's connections
6231
        //    are stored (done by either sourceChanged or updateOtherEndpoint)
6232
        //
6233
        this.rehomeEndpoint = function(ep, currentId, element) {
6234
            var eps = _amEndpoints[currentId] || [], 
6235
                elementId = jsPlumbInstance.getId(element);
6236
                
6237
            if (elementId !== currentId) {
6238
                var idx = jsPlumbUtil.indexOf(eps, ep);
6239
                if (idx > -1) {
6240
                    var _ep = eps.splice(idx, 1)[0];
6241
                    self.add(_ep, elementId);
6242
                }
6243
            }
6244
6245
            for (var i = 0; i < ep.connections.length; i++) {                
6246
                if (ep.connections[i].sourceId == currentId) {
6247
                    ep.connections[i].sourceId = ep.elementId;
6248
                    ep.connections[i].source = ep.element;                  
6249
                    self.sourceChanged(currentId, ep.elementId, ep.connections[i]);
6250
                }
6251
                else if(ep.connections[i].targetId == currentId) {
6252
                    ep.connections[i].targetId = ep.elementId;
6253
                    ep.connections[i].target = ep.element;   
6254
                    self.updateOtherEndpoint(ep.connections[i].sourceId, currentId, ep.elementId, ep.connections[i]);               
6255
                }
6256
            }   
6257
        };
6258
6259
		this.redraw = function(elementId, ui, timestamp, offsetToUI, clearEdits, doNotRecalcEndpoint) {
6260
		
6261
			if (!jsPlumbInstance.isSuspendDrawing()) {
6262
				// get all the endpoints for this element
6263
				var ep = _amEndpoints[elementId] || [],
6264
					endpointConnections = connectionsByElementId[elementId] || [],
6265
					connectionsToPaint = [],
6266
					endpointsToPaint = [],
6267
	                anchorsToUpdate = [];
6268
	            
6269
				timestamp = timestamp || jsPlumbInstance.timestamp();
6270
				// offsetToUI are values that would have been calculated in the dragManager when registering
6271
				// an endpoint for an element that had a parent (somewhere in the hierarchy) that had been
6272
				// registered as draggable.
6273
				offsetToUI = offsetToUI || {left:0, top:0};
6274
				if (ui) {
6275
					ui = {
6276
						left:ui.left + offsetToUI.left,
6277
						top:ui.top + offsetToUI.top
6278
					};
6279
				}
6280
									
6281
				// valid for one paint cycle.
6282
				var myOffset = jsPlumbInstance.updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp }),
6283
	                orientationCache = {};
6284
				
6285
				// actually, first we should compute the orientation of this element to all other elements to which
6286
				// this element is connected with a continuous anchor (whether both ends of the connection have
6287
				// a continuous anchor or just one)
6288
	                        
6289
	            for (var i = 0; i < endpointConnections.length; i++) {
6290
	                var conn = endpointConnections[i][0],
6291
						sourceId = conn.sourceId,
6292
	                    targetId = conn.targetId,
6293
	                    sourceContinuous = conn.endpoints[0].anchor.isContinuous,
6294
	                    targetContinuous = conn.endpoints[1].anchor.isContinuous;
6295
	
6296
	                if (sourceContinuous || targetContinuous) {
6297
		                var oKey = sourceId + "_" + targetId,
6298
		                    oKey2 = targetId + "_" + sourceId,
6299
		                    o = orientationCache[oKey],
6300
		                    oIdx = conn.sourceId == elementId ? 1 : 0;
6301
	
6302
		                if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] };
6303
		                if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] };
6304
	
6305
		                if (elementId != targetId) jsPlumbInstance.updateOffset( { elId : targetId, timestamp : timestamp }); 
6306
		                if (elementId != sourceId) jsPlumbInstance.updateOffset( { elId : sourceId, timestamp : timestamp }); 
6307
	
6308
		                var td = jsPlumbInstance.getCachedData(targetId),
6309
							sd = jsPlumbInstance.getCachedData(sourceId);
6310
	
6311
		                if (targetId == sourceId && (sourceContinuous || targetContinuous)) {
6312
		                    // here we may want to improve this by somehow determining the face we'd like
6313
						    // to put the connector on.  ideally, when drawing, the face should be calculated
6314
						    // by determining which face is closest to the point at which the mouse button
6315
							// was released.  for now, we're putting it on the top face.                            
6316
		                    _updateAnchorList(
6317
                                anchorLists[sourceId], 
6318
                                -Math.PI / 2, 
6319
                                0, 
6320
                                conn, 
6321
                                false, 
6322
                                targetId, 
6323
                                0, false, "top", sourceId, connectionsToPaint, endpointsToPaint);
6324
						}
6325
		                else {
6326
		                    if (!o) {
6327
		                        o = calculateOrientation(sourceId, targetId, sd.o, td.o, conn.endpoints[0].anchor, conn.endpoints[1].anchor);
6328
		                        orientationCache[oKey] = o;
6329
		                        // this would be a performance enhancement, but the computed angles need to be clamped to
6330
		                        //the (-PI/2 -> PI/2) range in order for the sorting to work properly.
6331
		                    /*  orientationCache[oKey2] = {
6332
		                            orientation:o.orientation,
6333
		                            a:[o.a[1], o.a[0]],
6334
		                            theta:o.theta + Math.PI,
6335
		                            theta2:o.theta2 + Math.PI
6336
		                        };*/
6337
		                    }
6338
		                    if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint);
6339
		                    if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint);
6340
		                }
6341
	
6342
		                if (sourceContinuous) jsPlumbUtil.addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; });
6343
		                if (targetContinuous) jsPlumbUtil.addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; });
6344
		                jsPlumbUtil.addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; });
6345
		                if ((sourceContinuous && oIdx === 0) || (targetContinuous && oIdx === 1))
6346
		                	jsPlumbUtil.addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; });
6347
		            }
6348
	            }				
6349
				// place Endpoints whose anchors are continuous but have no Connections
6350
				for (i = 0; i < ep.length; i++) {
6351
					if (ep[i].connections.length === 0 && ep[i].anchor.isContinuous) {
6352
						if (!anchorLists[elementId]) anchorLists[elementId] = { top:[], right:[], bottom:[], left:[] };
6353
						_updateAnchorList(anchorLists[elementId], -Math.PI / 2, 0, {endpoints:[ep[i], ep[i]], paint:function(){}}, false, elementId, 0, false, "top", elementId, connectionsToPaint, endpointsToPaint);
6354
						jsPlumbUtil.addWithFunction(anchorsToUpdate, elementId, function(a) { return a === elementId; });
6355
					}
6356
				}
6357
	            // now place all the continuous anchors we need to;
6358
	            for (i = 0; i < anchorsToUpdate.length; i++) {
6359
					placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]);
6360
				}
6361
6362
				// now that continuous anchors have been placed, paint all the endpoints for this element
6363
	            // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next
6364
	            // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way.
6365
				for (i = 0; i < ep.length; i++) {				
6366
                    ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myOffset.s, recalc:doNotRecalcEndpoint !== true });
6367
				}
6368
	            // ... and any other endpoints we came across as a result of the continuous anchors.
6369
	            for (i = 0; i < endpointsToPaint.length; i++) {
6370
                    var cd = jsPlumbInstance.getCachedData(endpointsToPaint[i].elementId);
6371
                    // dont use timestamp for this endpoint, as it is not for the current element and we may 
6372
                    // have needed to recalculate anchor position due to the element for the endpoint moving.
6373
                    //endpointsToPaint[i].paint( { timestamp : null, offset : cd, dimensions : cd.s });
6374
6375
                    endpointsToPaint[i].paint( { timestamp : timestamp, offset : cd, dimensions : cd.s });
6376
				}
6377
6378
				// paint all the standard and "dynamic connections", which are connections whose other anchor is
6379
				// static and therefore does need to be recomputed; we make sure that happens only one time.
6380
	
6381
				// TODO we could have compiled a list of these in the first pass through connections; might save some time.
6382
				for (i = 0; i < endpointConnections.length; i++) {
6383
					var otherEndpoint = endpointConnections[i][1];
6384
					if (otherEndpoint.anchor.constructor == jsPlumb.DynamicAnchor) {			 							
6385
						otherEndpoint.paint({ elementWithPrecedence:elementId, timestamp:timestamp });								
6386
	                    jsPlumbUtil.addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
6387
						// all the connections for the other endpoint now need to be repainted
6388
						for (var k = 0; k < otherEndpoint.connections.length; k++) {
6389
							if (otherEndpoint.connections[k] !== endpointConnections[i][0])							
6390
	                            jsPlumbUtil.addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; });
6391
						}
6392
					} else if (otherEndpoint.anchor.constructor == jsPlumb.Anchor) {					
6393
	                    jsPlumbUtil.addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
6394
					}
6395
				}
6396
				// paint current floating connection for this element, if there is one.
6397
				var fc = floatingConnections[elementId];
6398
				if (fc) 
6399
					fc.paint({timestamp:timestamp, recalc:false, elId:elementId});
6400
				                
6401
				// paint all the connections
6402
				for (i = 0; i < connectionsToPaint.length; i++) {
6403
					// if not a connection between the two elements in question dont use the timestamp.
6404
                    var ts  =timestamp;// ((connectionsToPaint[i].sourceId == sourceId && connectionsToPaint[i].targetId == targetId) ||
6405
                               //(connectionsToPaint[i].sourceId == targetId && connectionsToPaint[i].targetId == sourceId)) ? timestamp : null;
6406
                    connectionsToPaint[i].paint({elId:elementId, timestamp:ts, recalc:false, clearEdits:clearEdits});
6407
				}
6408
			}
6409
		};        
6410
        
6411
        var ContinuousAnchor = function(anchorParams) {
6412
            jsPlumbUtil.EventGenerator.apply(this);
6413
            this.type = "Continuous";
6414
            this.isDynamic = true;
6415
            this.isContinuous = true;
6416
            var faces = anchorParams.faces || ["top", "right", "bottom", "left"],
6417
                clockwise = !(anchorParams.clockwise === false),
6418
                availableFaces = { },
6419
                opposites = { "top":"bottom", "right":"left","left":"right","bottom":"top" },
6420
                clockwiseOptions = { "top":"right", "right":"bottom","left":"top","bottom":"left" },
6421
                antiClockwiseOptions = { "top":"left", "right":"top","left":"bottom","bottom":"right" },
6422
                secondBest = clockwise ? clockwiseOptions : antiClockwiseOptions,
6423
                lastChoice = clockwise ? antiClockwiseOptions : clockwiseOptions,
6424
                cssClass = anchorParams.cssClass || "";
6425
            
6426
            for (var i = 0; i < faces.length; i++) { availableFaces[faces[i]] = true; }
6427
          
6428
            // if the given edge is supported, returns it. otherwise looks for a substitute that _is_
6429
            // supported. if none supported we also return the request edge.
6430
            this.verifyEdge = function(edge) {
6431
                if (availableFaces[edge]) return edge;
6432
                else if (availableFaces[opposites[edge]]) return opposites[edge];
6433
                else if (availableFaces[secondBest[edge]]) return secondBest[edge];
6434
                else if (availableFaces[lastChoice[edge]]) return lastChoice[edge];
6435
                return edge; // we have to give them something.
6436
            };
6437
            
6438
            this.compute = function(params) {
6439
                return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0,0];
6440
            };
6441
            this.getCurrentLocation = function(params) {
6442
                return userDefinedContinuousAnchorLocations[params.element.id] || continuousAnchorLocations[params.element.id] || [0,0];
6443
            };
6444
            this.getOrientation = function(endpoint) {
6445
                return continuousAnchorOrientations[endpoint.id] || [0,0];
6446
            };
6447
            this.clearUserDefinedLocation = function() { 
6448
                delete userDefinedContinuousAnchorLocations[anchorParams.elementId]; 
6449
            };
6450
            this.setUserDefinedLocation = function(loc) { 
6451
                userDefinedContinuousAnchorLocations[anchorParams.elementId] = loc; 
6452
            };            
6453
            this.getCssClass = function() { return cssClass; };
6454
            this.setCssClass = function(c) { cssClass = c; };
6455
        };        
6456
        
6457
        // continuous anchors
6458
        jsPlumbInstance.continuousAnchorFactory = {
6459
            get:function(params) {
6460
                var existing = continuousAnchors[params.elementId];
6461
                if (!existing) {
6462
                    existing = new ContinuousAnchor(params);                    
6463
                    continuousAnchors[params.elementId] = existing;
6464
                }
6465
                return existing;
6466
            },
6467
            clear:function(elementId) {
6468
                delete continuousAnchors[elementId];
6469
            }
6470
        };
6471
	};
6472
    
6473
    /**
6474
     * Anchors model a position on some element at which an Endpoint may be located.  They began as a first class citizen of jsPlumb, ie. a user
6475
     * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"),
6476
     * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle").  jsPlumb now handles all of the
6477
     * creation of Anchors without user intervention.
6478
     */
6479
    jsPlumb.Anchor = function(params) {       
6480
        this.x = params.x || 0;
6481
        this.y = params.y || 0;
6482
        this.elementId = params.elementId;  
6483
        this.cssClass = params.cssClass || "";      
6484
        this.userDefinedLocation = null;
6485
        this.orientation = params.orientation || [ 0, 0 ];
6486
6487
        jsPlumbUtil.EventGenerator.apply(this);
6488
        
6489
        var jsPlumbInstance = params.jsPlumbInstance;//,
6490
            //lastTimestamp = null;//, lastReturnValue = null;
6491
        
6492
        this.lastReturnValue = null;
6493
        this.offsets = params.offsets || [ 0, 0 ];
6494
        this.timestamp = null;        
6495
        this.compute = function(params) {
6496
            
6497
            var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp; 
6498
6499
            if(params.clearUserDefinedLocation)
6500
                this.userDefinedLocation = null;
6501
            
6502
            if (timestamp && timestamp === self.timestamp)
6503
                return this.lastReturnValue;        
6504
            
6505
            if (this.userDefinedLocation != null) {
6506
                this.lastReturnValue = this.userDefinedLocation;
6507
            }
6508
            else {                
6509
                
6510
                this.lastReturnValue = [ xy[0] + (this.x * wh[0]) + this.offsets[0], xy[1] + (this.y * wh[1]) + this.offsets[1] ];                    
6511
                // adjust loc if there is an offsetParent
6512
                this.lastReturnValue = jsPlumbInstance.adjustForParentOffsetAndScroll(this.lastReturnValue, element.canvas);
6513
            }
6514
            
6515
            this.timestamp = timestamp;
6516
            return this.lastReturnValue;
6517
        };
6518
6519
        this.getCurrentLocation = function(params) { 
6520
            return (this.lastReturnValue == null || (params.timestamp != null && this.timestamp != params.timestamp)) ? this.compute(params) : this.lastReturnValue; 
6521
        };
6522
    };
6523
    jsPlumbUtil.extend(jsPlumb.Anchor, jsPlumbUtil.EventGenerator, {
6524
        equals : function(anchor) {
6525
            if (!anchor) return false;
6526
            var ao = anchor.getOrientation(),
6527
                o = this.getOrientation();
6528
            return this.x == anchor.x && this.y == anchor.y && this.offsets[0] == anchor.offsets[0] && this.offsets[1] == anchor.offsets[1] && o[0] == ao[0] && o[1] == ao[1];
6529
        },
6530
        getUserDefinedLocation : function() { 
6531
            return this.userDefinedLocation;
6532
        },        
6533
        setUserDefinedLocation : function(l) {
6534
            this.userDefinedLocation = l;
6535
        },
6536
        clearUserDefinedLocation : function() {
6537
            this.userDefinedLocation = null;
6538
        },
6539
        getOrientation : function(_endpoint) { return this.orientation; },
6540
        getCssClass : function() { return this.cssClass; }
6541
    });
6542
6543
    /**
6544
     * An Anchor that floats. its orientation is computed dynamically from
6545
     * its position relative to the anchor it is floating relative to.  It is used when creating 
6546
     * a connection through drag and drop.
6547
     * 
6548
     * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly.
6549
     */
6550
    jsPlumb.FloatingAnchor = function(params) {
6551
        
6552
        jsPlumb.Anchor.apply(this, arguments);
6553
6554
        // this is the anchor that this floating anchor is referenced to for
6555
        // purposes of calculating the orientation.
6556
        var ref = params.reference,
6557
            jpcl = jsPlumb.CurrentLibrary,
6558
            jsPlumbInstance = params.jsPlumbInstance,
6559
            // the canvas this refers to.
6560
            refCanvas = params.referenceCanvas,
6561
            size = jpcl.getSize(jpcl.getElementObject(refCanvas)),
6562
            // these are used to store the current relative position of our
6563
            // anchor wrt the reference anchor. they only indicate
6564
            // direction, so have a value of 1 or -1 (or, very rarely, 0). these
6565
            // values are written by the compute method, and read
6566
            // by the getOrientation method.
6567
            xDir = 0, yDir = 0,
6568
            // temporary member used to store an orientation when the floating
6569
            // anchor is hovering over another anchor.
6570
            orientation = null,
6571
            _lastResult = null;
6572
6573
        // clear from parent. we want floating anchor orientation to always be computed.
6574
        this.orientation = null;
6575
6576
        // set these to 0 each; they are used by certain types of connectors in the loopback case,
6577
        // when the connector is trying to clear the element it is on. but for floating anchor it's not
6578
        // very important.
6579
        this.x = 0; this.y = 0;
6580
6581
        this.isFloating = true;
6582
6583
        this.compute = function(params) {
6584
            var xy = params.xy, element = params.element,
6585
            result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy.
6586
                        
6587
            // adjust loc if there is an offsetParent
6588
            result = jsPlumbInstance.adjustForParentOffsetAndScroll(result, element.canvas);
6589
            
6590
            _lastResult = result;
6591
            return result;
6592
        };
6593
6594
        this.getOrientation = function(_endpoint) {
6595
            if (orientation) return orientation;
6596
            else {
6597
                var o = ref.getOrientation(_endpoint);
6598
                // here we take into account the orientation of the other
6599
                // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come
6600
                // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense.
6601
                return [ Math.abs(o[0]) * xDir * -1,
6602
                        Math.abs(o[1]) * yDir * -1 ];
6603
            }
6604
        };
6605
6606
        /**
6607
         * notification the endpoint associated with this anchor is hovering
6608
         * over another anchor; we want to assume that anchor's orientation
6609
         * for the duration of the hover.
6610
         */
6611
        this.over = function(anchor, endpoint) { 
6612
            orientation = anchor.getOrientation(endpoint); 
6613
        };
6614
6615
        /**
6616
         * notification the endpoint associated with this anchor is no
6617
         * longer hovering over another anchor; we should resume calculating
6618
         * orientation as we normally do.
6619
         */
6620
        this.out = function() { orientation = null; };
6621
6622
        this.getCurrentLocation = function(params) { return _lastResult == null ? this.compute(params) : _lastResult; };
6623
    };
6624
    jsPlumbUtil.extend(jsPlumb.FloatingAnchor, jsPlumb.Anchor);
6625
6626
    var _convertAnchor = function(anchor, jsPlumbInstance, elementId) { 
6627
        return anchor.constructor == jsPlumb.Anchor ? anchor: jsPlumbInstance.makeAnchor(anchor, elementId, jsPlumbInstance); 
6628
    };
6629
6630
    /* 
6631
     * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles
6632
     * through at compute time to find the one that is located closest to
6633
     * the center of the target element, and returns that Anchor's compute
6634
     * method result. this causes endpoints to follow each other with
6635
     * respect to the orientation of their target elements, which is a useful
6636
     * feature for some applications.
6637
     * 
6638
     */
6639
    jsPlumb.DynamicAnchor = function(params) {
6640
        jsPlumb.Anchor.apply(this, arguments);
6641
        
6642
        this.isSelective = true;
6643
        this.isDynamic = true;			
6644
        this.anchors = [];
6645
        this.elementId = params.elementId;
6646
        this.jsPlumbInstance = params.jsPlumbInstance;
6647
6648
        for (var i = 0; i < params.anchors.length; i++) 
6649
            this.anchors[i] = _convertAnchor(params.anchors[i], this.jsPlumbInstance, this.elementId);			
6650
        this.addAnchor = function(anchor) { this.anchors.push(_convertAnchor(anchor, this.jsPlumbInstance, this.elementId)); };
6651
        this.getAnchors = function() { return this.anchors; };
6652
        this.locked = false;
6653
        var _curAnchor = this.anchors.length > 0 ? this.anchors[0] : null,
6654
            _curIndex = this.anchors.length > 0 ? 0 : -1,
6655
            _lastAnchor = _curAnchor,
6656
            self = this,
6657
        
6658
            // helper method to calculate the distance between the centers of the two elements.
6659
            _distance = function(anchor, cx, cy, xy, wh) {
6660
                var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]),				
6661
                    acx = xy[0] + (wh[0] / 2), acy = xy[1] + (wh[1] / 2);
6662
                return (Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2)) +
6663
                        Math.sqrt(Math.pow(acx - ax, 2) + Math.pow(acy - ay, 2)));
6664
            },        
6665
            // default method uses distance between element centers.  you can provide your own method in the dynamic anchor
6666
            // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays: 
6667
            // xy - xy loc of the anchor's element
6668
            // wh - anchor's element's dimensions
6669
            // txy - xy loc of the element of the other anchor in the connection
6670
            // twh - dimensions of the element of the other anchor in the connection.
6671
            // anchors - the list of selectable anchors
6672
            _anchorSelector = params.selector || function(xy, wh, txy, twh, anchors) {
6673
                var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2);
6674
                var minIdx = -1, minDist = Infinity;
6675
                for ( var i = 0; i < anchors.length; i++) {
6676
                    var d = _distance(anchors[i], cx, cy, xy, wh);
6677
                    if (d < minDist) {
6678
                        minIdx = i + 0;
6679
                        minDist = d;
6680
                    }
6681
                }
6682
                return anchors[minIdx];
6683
            };
6684
        
6685
        this.compute = function(params) {				
6686
            var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh;				
6687
            
6688
            if(params.clearUserDefinedLocation)
6689
                userDefinedLocation = null;
6690
6691
            this.timestamp = timestamp;            
6692
            
6693
            var udl = self.getUserDefinedLocation();
6694
            if (udl != null) {
6695
                return udl;
6696
            }
6697
            
6698
            // if anchor is locked or an opposite element was not given, we
6699
            // maintain our state. anchor will be locked
6700
            // if it is the source of a drag and drop.
6701
            if (this.locked || txy == null || twh == null)
6702
                return _curAnchor.compute(params);				
6703
            else
6704
                params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute.
6705
            
6706
            _curAnchor = _anchorSelector(xy, wh, txy, twh, this.anchors);
6707
            this.x = _curAnchor.x;
6708
            this.y = _curAnchor.y;        
6709
6710
            if (_curAnchor != _lastAnchor)
6711
                this.fire("anchorChanged", _curAnchor);
6712
6713
            _lastAnchor = _curAnchor;
6714
            
6715
            return _curAnchor.compute(params);
6716
        };
6717
6718
        this.getCurrentLocation = function(params) {
6719
            return this.getUserDefinedLocation() || (_curAnchor != null ? _curAnchor.getCurrentLocation(params) : null);
6720
        };
6721
6722
        this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; };
6723
        this.over = function(anchor, endpoint) { if (_curAnchor != null) _curAnchor.over(anchor, endpoint); };
6724
        this.out = function() { if (_curAnchor != null) _curAnchor.out(); };
6725
6726
        this.getCssClass = function() { return (_curAnchor && _curAnchor.getCssClass()) || ""; };
6727
    };    
6728
    jsPlumbUtil.extend(jsPlumb.DynamicAnchor, jsPlumb.Anchor);        
6729
    
6730
// -------- basic anchors ------------------    
6731
    var _curryAnchor = function(x, y, ox, oy, type, fnInit) {
6732
        jsPlumb.Anchors[type] = function(params) {
6733
            var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
6734
            a.type = type;
6735
            if (fnInit) fnInit(a, params);
6736
            return a;
6737
        };
6738
    };
6739
    	
6740
	_curryAnchor(0.5, 0, 0,-1, "TopCenter");
6741
    _curryAnchor(0.5, 1, 0, 1, "BottomCenter");
6742
    _curryAnchor(0, 0.5, -1, 0, "LeftMiddle");
6743
    _curryAnchor(1, 0.5, 1, 0, "RightMiddle");
6744
    // from 1.4.2: Top, Right, Bottom, Left
6745
    _curryAnchor(0.5, 0, 0,-1, "Top");
6746
    _curryAnchor(0.5, 1, 0, 1, "Bottom");
6747
    _curryAnchor(0, 0.5, -1, 0, "Left");
6748
    _curryAnchor(1, 0.5, 1, 0, "Right");
6749
    _curryAnchor(0.5, 0.5, 0, 0, "Center");
6750
    _curryAnchor(1, 0, 0,-1, "TopRight");
6751
    _curryAnchor(1, 1, 0, 1, "BottomRight");
6752
    _curryAnchor(0, 0, 0, -1, "TopLeft");
6753
    _curryAnchor(0, 1, 0, 1, "BottomLeft");
6754
    
6755
// ------- dynamic anchors -------------------    
6756
			
6757
    // default dynamic anchors chooses from Top, Right, Bottom, Left
6758
	jsPlumb.Defaults.DynamicAnchors = function(params) {
6759
		return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance);
6760
	};
6761
    
6762
    // default dynamic anchors bound to name 'AutoDefault'
6763
	jsPlumb.Anchors.AutoDefault  = function(params) { 
6764
		var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params));
6765
		a.type = "AutoDefault";
6766
		return a;
6767
	};	
6768
    
6769
// ------- continuous anchors -------------------    
6770
    
6771
    var _curryContinuousAnchor = function(type, faces) {
6772
        jsPlumb.Anchors[type] = function(params) {
6773
            var a = params.jsPlumbInstance.makeAnchor(["Continuous", { faces:faces }], params.elementId, params.jsPlumbInstance);
6774
            a.type = type;
6775
            return a;
6776
        };
6777
    };
6778
    
6779
    jsPlumb.Anchors.Continuous = function(params) {
6780
		return params.jsPlumbInstance.continuousAnchorFactory.get(params);
6781
	};
6782
                
6783
    _curryContinuousAnchor("ContinuousLeft", ["left"]);    
6784
    _curryContinuousAnchor("ContinuousTop", ["top"]);                 
6785
    _curryContinuousAnchor("ContinuousBottom", ["bottom"]);                 
6786
    _curryContinuousAnchor("ContinuousRight", ["right"]); 
6787
    
6788
// ------- position assign anchors -------------------    
6789
    
6790
    // this anchor type lets you assign the position at connection time.
6791
	_curryAnchor(0, 0, 0, 0, "Assign", function(anchor, params) {
6792
		// find what to use as the "position finder". the user may have supplied a String which represents
6793
		// the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the
6794
		// position finder as a function.  we find out what to use and then set it on the anchor.
6795
		var pf = params.position || "Fixed";
6796
		anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf;
6797
		// always set the constructor params; the position finder might need them later (the Grid one does,
6798
		// for example)
6799
		anchor.constructorParams = params;
6800
	});	
6801
6802
    // these are the default anchor positions finders, which are used by the makeTarget function.  supplying
6803
    // a position finder argument to that function allows you to specify where the resulting anchor will
6804
    // be located
6805
	jsPlumbInstance.prototype.AnchorPositionFinders = {
6806
		"Fixed": function(dp, ep, es, params) {
6807
			return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ];	
6808
		},
6809
		"Grid":function(dp, ep, es, params) {
6810
			var dx = dp.left - ep.left, dy = dp.top - ep.top,
6811
				gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]),
6812
				mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
6813
			return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ];
6814
		}
6815
	};
6816
    
6817
// ------- perimeter anchors -------------------    
6818
		
6819
	jsPlumb.Anchors.Perimeter = function(params) {
6820
		params = params || {};
6821
		var anchorCount = params.anchorCount || 60,
6822
			shape = params.shape;
6823
		
6824
		if (!shape) throw new Error("no shape supplied to Perimeter Anchor type");		
6825
		
6826
		var _circle = function() {
6827
                var r = 0.5, step = Math.PI * 2 / anchorCount, current = 0, a = [];
6828
                for (var i = 0; i < anchorCount; i++) {
6829
                    var x = r + (r * Math.sin(current)),
6830
                        y = r + (r * Math.cos(current));                                
6831
                    a.push( [ x, y, 0, 0 ] );
6832
                    current += step;
6833
                }
6834
                return a;	
6835
            },
6836
            _path = function(segments) {
6837
                var anchorsPerFace = anchorCount / segments.length, a = [],
6838
                    _computeFace = function(x1, y1, x2, y2, fractionalLength) {
6839
                        anchorsPerFace = anchorCount * fractionalLength;
6840
                        var dx = (x2 - x1) / anchorsPerFace, dy = (y2 - y1) / anchorsPerFace;
6841
                        for (var i = 0; i < anchorsPerFace; i++) {
6842
                            a.push( [
6843
                                x1 + (dx * i),
6844
                                y1 + (dy * i),
6845
                                0,
6846
                                0
6847
                            ]);
6848
                        }
6849
                    };
6850
								
6851
                for (var i = 0; i < segments.length; i++)
6852
                    _computeFace.apply(null, segments[i]);
6853
														
6854
                return a;					
6855
            },
6856
			_shape = function(faces) {												
6857
                var s = [];
6858
                for (var i = 0; i < faces.length; i++) {
6859
                    s.push([faces[i][0], faces[i][1], faces[i][2], faces[i][3], 1 / faces.length]);
6860
                }
6861
                return _path(s);
6862
			},
6863
			_rectangle = function() {
6864
				return _shape([
6865
					[ 0, 0, 1, 0 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0, 0 ]
6866
				]);		
6867
			};
6868
		
6869
		var _shapes = {
6870
			"Circle":_circle,
6871
			"Ellipse":_circle,
6872
			"Diamond":function() {
6873
				return _shape([
6874
						[ 0.5, 0, 1, 0.5 ], [ 1, 0.5, 0.5, 1 ], [ 0.5, 1, 0, 0.5 ], [ 0, 0.5, 0.5, 0 ]
6875
				]);
6876
			},
6877
			"Rectangle":_rectangle,
6878
			"Square":_rectangle,
6879
			"Triangle":function() {
6880
				return _shape([
6881
						[ 0.5, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 0, 1, 0.5, 0]
6882
				]);	
6883
			},
6884
			"Path":function(params) {
6885
                var points = params.points, p = [], tl = 0;
6886
				for (var i = 0; i < points.length - 1; i++) {
6887
                    var l = Math.sqrt(Math.pow(points[i][2] - points[i][0]) + Math.pow(points[i][3] - points[i][1]));
6888
                    tl += l;
6889
					p.push([points[i][0], points[i][1], points[i+1][0], points[i+1][1], l]);						
6890
				}
6891
                for (var j = 0; j < p.length; j++) {
6892
                    p[j][4] = p[j][4] / tl;
6893
                }
6894
				return _path(p);
6895
			}
6896
		},
6897
        _rotate = function(points, amountInDegrees) {
6898
            var o = [], theta = amountInDegrees / 180 * Math.PI ;
6899
            for (var i = 0; i < points.length; i++) {
6900
                var _x = points[i][0] - 0.5,
6901
                    _y = points[i][1] - 0.5;
6902
                    
6903
                o.push([
6904
                    0.5 + ((_x * Math.cos(theta)) - (_y * Math.sin(theta))),
6905
                    0.5 + ((_x * Math.sin(theta)) + (_y * Math.cos(theta))),
6906
                    points[i][2],
6907
                    points[i][3]
6908
                ]);
6909
            }
6910
            return o;
6911
        };
6912
		
6913
		if (!_shapes[shape]) throw new Error("Shape [" + shape + "] is unknown by Perimeter Anchor type");
6914
		
6915
		var da = _shapes[shape](params);
6916
        if (params.rotation) da = _rotate(da, params.rotation);
6917
        var a = params.jsPlumbInstance.makeDynamicAnchor(da);
6918
		a.type = "Perimeter";
6919
		return a;
6920
	};
6921
})();
6922
/*
6923
 * jsPlumb
6924
 * 
6925
 * Title:jsPlumb 1.5.5
6926
 * 
6927
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
6928
 * elements, or VML.  
6929
 * 
6930
 * This file contains the default Connectors, Endpoint and Overlay definitions.
6931
 *
6932
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
6933
 * 
6934
 * http://jsplumb.org
6935
 * http://github.com/sporritt/jsplumb
6936
 * http://code.google.com/p/jsplumb
6937
 * 
6938
 * Dual licensed under the MIT and GPL2 licenses.
6939
 */
6940
6941
;(function() {	
6942
				
6943
	/**
6944
	 * 
6945
	 * Helper class to consume unused mouse events by components that are DOM elements and
6946
	 * are used by all of the different rendering modes.
6947
	 * 
6948
	 */
6949
	jsPlumb.DOMElementComponent = jsPlumbUtil.extend(jsPlumb.jsPlumbUIComponent, function(params) {		
6950
		// when render mode is canvas, these functions may be called by the canvas mouse handler.  
6951
		// this component is safe to pipe this stuff to /dev/null.
6952
		this.mousemove = 
6953
		this.dblclick  = 
6954
		this.click = 
6955
		this.mousedown = 
6956
		this.mouseup = function(e) { };					
6957
	});
6958
	
6959
	jsPlumb.Segments = {
6960
        	
6961
        /*
6962
         * Class: AbstractSegment
6963
         * A Connector is made up of 1..N Segments, each of which has a Type, such as 'Straight', 'Arc',
6964
         * 'Bezier'. This is new from 1.4.2, and gives us a lot more flexibility when drawing connections: things such
6965
         * as rounded corners for flowchart connectors, for example, or a straight line stub for Bezier connections, are
6966
         * much easier to do now.
6967
         *
6968
         * A Segment is responsible for providing coordinates for painting it, and also must be able to report its length.
6969
         * 
6970
         */ 
6971
        AbstractSegment : function(params) { 
6972
            this.params = params;
6973
            
6974
            /**
6975
            * Function: findClosestPointOnPath
6976
            * Finds the closest point on this segment to the given [x, y], 
6977
            * returning both the x and y of the point plus its distance from
6978
            * the supplied point, and its location along the length of the
6979
            * path inscribed by the segment.  This implementation returns
6980
            * Infinity for distance and null values for everything else;
6981
            * subclasses are expected to override.
6982
            */
6983
            this.findClosestPointOnPath = function(x, y) {
6984
                return {
6985
                    d:Infinity,
6986
                    x:null,
6987
                    y:null,
6988
                    l:null
6989
                };
6990
            };
6991
6992
            this.getBounds = function() {
6993
                return {
6994
                    minX:Math.min(params.x1, params.x2),
6995
                    minY:Math.min(params.y1, params.y2),
6996
                    maxX:Math.max(params.x1, params.x2),
6997
                    maxY:Math.max(params.y1, params.y2)
6998
                };
6999
            };
7000
        },
7001
        Straight : function(params) {
7002
            var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
7003
                length, m, m2, x1, x2, y1, y2,
7004
                _recalc = function() {
7005
                    length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
7006
                    m = jsPlumbGeom.gradient({x:x1, y:y1}, {x:x2, y:y2});
7007
                    m2 = -1 / m;                
7008
                };
7009
                
7010
            this.type = "Straight";
7011
            
7012
            this.getLength = function() { return length; };
7013
            this.getGradient = function() { return m; };
7014
                
7015
            this.getCoordinates = function() {
7016
                return { x1:x1,y1:y1,x2:x2,y2:y2 };
7017
            };
7018
            this.setCoordinates = function(coords) {
7019
                x1 = coords.x1; y1 = coords.y1; x2 = coords.x2; y2 = coords.y2;
7020
                _recalc();
7021
            };
7022
            this.setCoordinates({x1:params.x1, y1:params.y1, x2:params.x2, y2:params.y2});
7023
7024
            this.getBounds = function() {
7025
                return {
7026
                    minX:Math.min(x1, x2),
7027
                    minY:Math.min(y1, y2),
7028
                    maxX:Math.max(x1, x2),
7029
                    maxY:Math.max(y1, y2)
7030
                };
7031
            };
7032
            
7033
            /**
7034
             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
7035
             * 0 to 1 inclusive. for the straight line segment this is simple maths.
7036
             */
7037
             this.pointOnPath = function(location, absolute) {
7038
                if (location === 0 && !absolute)
7039
                    return { x:x1, y:y1 };
7040
                else if (location == 1 && !absolute)
7041
                    return { x:x2, y:y2 };
7042
                else {
7043
                    var l = absolute ? location > 0 ? location : length + location : location * length;
7044
                    return jsPlumbGeom.pointOnLine({x:x1, y:y1}, {x:x2, y:y2}, l);
7045
                }
7046
            };
7047
            
7048
            /**
7049
             * returns the gradient of the segment at the given point - which for us is constant.
7050
             */
7051
            this.gradientAtPoint = function(_) {
7052
                return m;
7053
            };
7054
            
7055
            /**
7056
             * returns the point on the segment's path that is 'distance' along the length of the path from 'location', where 
7057
             * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
7058
             * this hands off to jsPlumbUtil to do the maths, supplying two points and the distance.
7059
             */            
7060
            this.pointAlongPathFrom = function(location, distance, absolute) {            
7061
                var p = this.pointOnPath(location, absolute),
7062
                    farAwayPoint = distance <= 0 ? {x:x1, y:y1} : {x:x2, y:y2 };
7063
7064
                /*
7065
                location == 1 ? {
7066
                                        x:x1 + ((x2 - x1) * 10),
7067
                                        y:y1 + ((y1 - y2) * 10)
7068
                                    } : 
7069
                */
7070
    
7071
                if (distance <= 0 && Math.abs(distance) > 1) distance *= -1;
7072
    
7073
                return jsPlumbGeom.pointOnLine(p, farAwayPoint, distance);
7074
            };
7075
            
7076
            // is c between a and b?
7077
            var within = function(a,b,c) {
7078
                return c >= Math.min(a,b) && c <= Math.max(a,b); 
7079
            };
7080
            // find which of a and b is closest to c
7081
            var closest = function(a,b,c) {
7082
                return Math.abs(c - a) < Math.abs(c - b) ? a : b;
7083
            };
7084
            
7085
            /**
7086
                Function: findClosestPointOnPath
7087
                Finds the closest point on this segment to [x,y]. See
7088
                notes on this method in AbstractSegment.
7089
            */
7090
            this.findClosestPointOnPath = function(x, y) {
7091
                var out = {
7092
                    d:Infinity,
7093
                    x:null,
7094
                    y:null,
7095
                    l:null,
7096
                    x1:x1,
7097
                    x2:x2,
7098
                    y1:y1,
7099
                    y2:y2
7100
                };
7101
7102
                if (m === 0) {                  
7103
                    out.y = y1;
7104
                    out.x = within(x1, x2, x) ? x : closest(x1, x2, x);
7105
                }
7106
                else if (m == Infinity || m == -Infinity) {
7107
                    out.x = x1;                
7108
                    out.y = within(y1, y2, y) ? y : closest(y1, y2, y);
7109
                }
7110
                else {
7111
                    // closest point lies on normal from given point to this line.  
7112
                    var b = y1 - (m * x1),
7113
                        b2 = y - (m2 * x),                    
7114
                    // y1 = m.x1 + b and y1 = m2.x1 + b2
7115
                    // so m.x1 + b = m2.x1 + b2
7116
                    // x1(m - m2) = b2 - b
7117
                    // x1 = (b2 - b) / (m - m2)
7118
                        _x1 = (b2 -b) / (m - m2),
7119
                        _y1 = (m * _x1) + b;
7120
                                        
7121
                    out.x = within(x1,x2,_x1) ? _x1 : closest(x1,x2,_x1);//_x1;
7122
                    out.y = within(y1,y2,_y1) ? _y1 : closest(y1,y2,_y1);//_y1;                    
7123
                }
7124
7125
                var fractionInSegment = jsPlumbGeom.lineLength([ out.x, out.y ], [ x1, y1 ]);
7126
                out.d = jsPlumbGeom.lineLength([x,y], [out.x, out.y]);
7127
                out.l = fractionInSegment / length;            
7128
                return out;
7129
            };        
7130
        },
7131
	
7132
        /*
7133
            Arc Segment. You need to supply:
7134
    
7135
            r   -   radius
7136
            cx  -   center x for the arc
7137
            cy  -   center y for the arc
7138
            ac  -   whether the arc is anticlockwise or not. default is clockwise.
7139
    
7140
            and then either:
7141
    
7142
            startAngle  -   startAngle for the arc.
7143
            endAngle    -   endAngle for the arc.
7144
    
7145
            or:
7146
    
7147
            x1          -   x for start point
7148
            y1          -   y for start point
7149
            x2          -   x for end point
7150
            y2          -   y for end point
7151
    
7152
        */
7153
        Arc : function(params) {
7154
            var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
7155
                _calcAngle = function(_x, _y) {
7156
                    return jsPlumbGeom.theta([params.cx, params.cy], [_x, _y]);    
7157
                },
7158
                _calcAngleForLocation = function(segment, location) {
7159
                    if (segment.anticlockwise) {
7160
                        var sa = segment.startAngle < segment.endAngle ? segment.startAngle + TWO_PI : segment.startAngle,
7161
                            s = Math.abs(sa - segment.endAngle);
7162
                        return sa - (s * location);                    
7163
                    }
7164
                    else {
7165
                        var ea = segment.endAngle < segment.startAngle ? segment.endAngle + TWO_PI : segment.endAngle,
7166
                            ss = Math.abs (ea - segment.startAngle);
7167
                    
7168
                        return segment.startAngle + (ss * location);
7169
                    }
7170
                },
7171
                TWO_PI = 2 * Math.PI;
7172
            
7173
            this.radius = params.r;
7174
            this.anticlockwise = params.ac;			
7175
            this.type = "Arc";
7176
                
7177
            if (params.startAngle && params.endAngle) {
7178
                this.startAngle = params.startAngle;
7179
                this.endAngle = params.endAngle;            
7180
                this.x1 = params.cx + (this.radius * Math.cos(params.startAngle));     
7181
                this.y1 = params.cy + (this.radius * Math.sin(params.startAngle));            
7182
                this.x2 = params.cx + (this.radius * Math.cos(params.endAngle));     
7183
                this.y2 = params.cy + (this.radius * Math.sin(params.endAngle));                        
7184
            }
7185
            else {
7186
                this.startAngle = _calcAngle(params.x1, params.y1);
7187
                this.endAngle = _calcAngle(params.x2, params.y2);            
7188
                this.x1 = params.x1;
7189
                this.y1 = params.y1;
7190
                this.x2 = params.x2;
7191
                this.y2 = params.y2;            
7192
            }
7193
            
7194
            if (this.endAngle < 0) this.endAngle += TWO_PI;
7195
            if (this.startAngle < 0) this.startAngle += TWO_PI;   
7196
7197
            // segment is used by vml     
7198
            this.segment = jsPlumbGeom.quadrant([this.x1, this.y1], [this.x2, this.y2]);
7199
            
7200
            // we now have startAngle and endAngle as positive numbers, meaning the
7201
            // absolute difference (|d|) between them is the sweep (s) of this arc, unless the
7202
            // arc is 'anticlockwise' in which case 's' is given by 2PI - |d|.
7203
            
7204
            var ea = this.endAngle < this.startAngle ? this.endAngle + TWO_PI : this.endAngle;
7205
            this.sweep = Math.abs (ea - this.startAngle);
7206
            if (this.anticlockwise) this.sweep = TWO_PI - this.sweep;
7207
            var circumference = 2 * Math.PI * this.radius,
7208
                frac = this.sweep / TWO_PI,
7209
                length = circumference * frac;
7210
            
7211
            this.getLength = function() {
7212
                return length;
7213
            };
7214
7215
            this.getBounds = function() {
7216
                return {
7217
                    minX:params.cx - params.r,
7218
                    maxX:params.cx + params.r,
7219
                    minY:params.cy - params.r,
7220
                    maxY:params.cy + params.r
7221
                };
7222
            };
7223
            
7224
            var VERY_SMALL_VALUE = 0.0000000001,
7225
                gentleRound = function(n) {
7226
                    var f = Math.floor(n), r = Math.ceil(n);
7227
                    if (n - f < VERY_SMALL_VALUE) 
7228
                        return f;    
7229
                    else if (r - n < VERY_SMALL_VALUE)
7230
                        return r;
7231
                    return n;
7232
                };
7233
            
7234
            /**
7235
             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
7236
             * 0 to 1 inclusive. 
7237
             */
7238
            this.pointOnPath = function(location, absolute) {            
7239
                
7240
                if (location === 0) {
7241
                    return { x:this.x1, y:this.y1, theta:this.startAngle };    
7242
                }
7243
                else if (location == 1) {
7244
                    return { x:this.x2, y:this.y2, theta:this.endAngle };                    
7245
                }
7246
                
7247
                if (absolute) {
7248
                    location = location / length;
7249
                }
7250
    
7251
                var angle = _calcAngleForLocation(this, location),
7252
                    _x = params.cx + (params.r * Math.cos(angle)),
7253
                    _y  = params.cy + (params.r * Math.sin(angle));					
7254
    
7255
                return { x:gentleRound(_x), y:gentleRound(_y), theta:angle };
7256
            };
7257
            
7258
            /**
7259
             * returns the gradient of the segment at the given point.
7260
             */
7261
            this.gradientAtPoint = function(location, absolute) {
7262
                var p = this.pointOnPath(location, absolute);
7263
                var m = jsPlumbGeom.normal( [ params.cx, params.cy ], [p.x, p.y ] );
7264
                if (!this.anticlockwise && (m == Infinity || m == -Infinity)) m *= -1;
7265
                return m;
7266
            };	              
7267
                    
7268
            this.pointAlongPathFrom = function(location, distance, absolute) {
7269
                var p = this.pointOnPath(location, absolute),
7270
                    arcSpan = distance / circumference * 2 * Math.PI,
7271
                    dir = this.anticlockwise ? -1 : 1,
7272
                    startAngle = p.theta + (dir * arcSpan),				
7273
                    startX = params.cx + (this.radius * Math.cos(startAngle)),
7274
                    startY = params.cy + (this.radius * Math.sin(startAngle));	
7275
    
7276
                return {x:startX, y:startY};
7277
            };	            
7278
        },
7279
	
7280
        Bezier : function(params) {
7281
            var _super = jsPlumb.Segments.AbstractSegment.apply(this, arguments),
7282
                curve = [	
7283
                    { x:params.x1, y:params.y1},
7284
                    { x:params.cp1x, y:params.cp1y },
7285
                    { x:params.cp2x, y:params.cp2y },
7286
                    { x:params.x2, y:params.y2 }
7287
                ],
7288
                // although this is not a strictly rigorous determination of bounds
7289
                // of a bezier curve, it works for the types of curves that this segment
7290
                // type produces.
7291
                bounds = {
7292
                    minX:Math.min(params.x1, params.x2, params.cp1x, params.cp2x),
7293
                    minY:Math.min(params.y1, params.y2, params.cp1y, params.cp2y),
7294
                    maxX:Math.max(params.x1, params.x2, params.cp1x, params.cp2x),
7295
                    maxY:Math.max(params.y1, params.y2, params.cp1y, params.cp2y)
7296
                };
7297
                
7298
            this.type = "Bezier";            
7299
            
7300
            var _translateLocation = function(_curve, location, absolute) {
7301
                if (absolute)
7302
                    location = jsBezier.locationAlongCurveFrom(_curve, location > 0 ? 0 : 1, location);
7303
    
7304
                return location;
7305
            };		
7306
            
7307
            /**
7308
             * returns the point on the segment's path that is 'location' along the length of the path, where 'location' is a decimal from
7309
             * 0 to 1 inclusive. 
7310
             */
7311
            this.pointOnPath = function(location, absolute) {
7312
                location = _translateLocation(curve, location, absolute);                
7313
                return jsBezier.pointOnCurve(curve, location);
7314
            };
7315
            
7316
            /**
7317
             * returns the gradient of the segment at the given point.
7318
             */
7319
            this.gradientAtPoint = function(location, absolute) {
7320
                location = _translateLocation(curve, location, absolute);
7321
                return jsBezier.gradientAtPoint(curve, location);        	
7322
            };	              
7323
            
7324
            this.pointAlongPathFrom = function(location, distance, absolute) {
7325
                location = _translateLocation(curve, location, absolute);
7326
                return jsBezier.pointAlongCurveFrom(curve, location, distance);
7327
            };
7328
            
7329
            this.getLength = function() {
7330
                return jsBezier.getLength(curve);				
7331
            };
7332
7333
            this.getBounds = function() {
7334
                return bounds;
7335
            };
7336
        }
7337
    };
7338
7339
    /*
7340
        Class: AbstractComponent
7341
        Superclass for AbstractConnector and AbstractEndpoint.
7342
    */
7343
    var AbstractComponent = function() {        
7344
        this.resetBounds = function() {
7345
            this.bounds = { minX:Infinity, minY:Infinity, maxX:-Infinity, maxY:-Infinity };
7346
        };
7347
        this.resetBounds();        
7348
    };
7349
	
7350
	/*
7351
	 * Class: AbstractConnector
7352
	 * Superclass for all Connectors; here is where Segments are managed.  This is exposed on jsPlumb just so it
7353
	 * can be accessed from other files. You should not try to instantiate one of these directly.
7354
	 *
7355
	 * When this class is asked for a pointOnPath, or gradient etc, it must first figure out which segment to dispatch
7356
	 * that request to. This is done by keeping track of the total connector length as segments are added, and also
7357
	 * their cumulative ratios to the total length.  Then when the right segment is found it is a simple case of dispatching
7358
	 * the request to it (and adjusting 'location' so that it is relative to the beginning of that segment.)
7359
	 */ 
7360
	jsPlumb.Connectors.AbstractConnector = function(params) {
7361
		
7362
        AbstractComponent.apply(this, arguments);        
7363
7364
		var //self = this, 
7365
            segments = [],
7366
            editing = false,
7367
			totalLength = 0,
7368
			segmentProportions = [],
7369
			segmentProportionalLengths = [],        
7370
            stub = params.stub || 0, 
7371
            sourceStub = jsPlumbUtil.isArray(stub) ? stub[0] : stub,
7372
            targetStub = jsPlumbUtil.isArray(stub) ? stub[1] : stub,
7373
            gap = params.gap || 0,
7374
            sourceGap = jsPlumbUtil.isArray(gap) ? gap[0] : gap,
7375
            targetGap = jsPlumbUtil.isArray(gap) ? gap[1] : gap,
7376
            userProvidedSegments = null,
7377
            edited = false,
7378
            paintInfo = null;            
7379
        
7380
        // subclasses should override.
7381
        this.isEditable = function() { return false; };                        
7382
        this.setEdited = function(ed) { edited = ed; };
7383
7384
        // to be overridden by subclasses.
7385
        this.getPath = function() { };
7386
        this.setPath = function(path) { };
7387
        
7388
        /**
7389
        * Function: findSegmentForPoint
7390
        * Returns the segment that is closest to the given [x,y],
7391
        * null if nothing found.  This function returns a JS 
7392
        * object with:
7393
        *
7394
        *   d   -   distance from segment
7395
        *   l   -   proportional location in segment
7396
        *   x   -   x point on the segment
7397
        *   y   -   y point on the segment
7398
        *   s   -   the segment itself.
7399
        */ 
7400
        this.findSegmentForPoint = function(x, y) {
7401
            var out = { d:Infinity, s:null, x:null, y:null, l:null };
7402
            for (var i = 0; i < segments.length; i++) {
7403
                var _s = segments[i].findClosestPointOnPath(x, y);
7404
                if (_s.d < out.d) {
7405
                    out.d = _s.d; 
7406
                    out.l = _s.l; 
7407
                    out.x = _s.x;
7408
                    out.y = _s.y; 
7409
                    out.s = segments[i];
7410
                    out.x1 = _s.x1;
7411
                    out.x2 = _s.x2;
7412
                    out.y1 = _s.y1;
7413
                    out.y2 = _s.y2;
7414
                    out.index = i;
7415
                }
7416
            }
7417
            
7418
            return out;                
7419
        };
7420
			
7421
		var _updateSegmentProportions = function() {
7422
                var curLoc = 0;
7423
                for (var i = 0; i < segments.length; i++) {
7424
                    var sl = segments[i].getLength();
7425
                    segmentProportionalLengths[i] = sl / totalLength;
7426
                    segmentProportions[i] = [curLoc, (curLoc += (sl / totalLength)) ];
7427
                }
7428
            },
7429
		
7430
            /**
7431
             * returns [segment, proportion of travel in segment, segment index] for the segment 
7432
             * that contains the point which is 'location' distance along the entire path, where 
7433
             * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths 
7434
             * are made up of a list of segments, each of which contributes some fraction to
7435
             * the total length. 
7436
             * From 1.3.10 this also supports the 'absolute' property, which lets us specify a location
7437
             * as the absolute distance in pixels, rather than a proportion of the total path. 
7438
             */
7439
            _findSegmentForLocation = function(location, absolute) {
7440
                if (absolute) {
7441
                    location = location > 0 ? location / totalLength : (totalLength + location) / totalLength;
7442
                }
7443
    
7444
                var idx = segmentProportions.length - 1, inSegmentProportion = 1;
7445
                //if (location < 1) {
7446
                    for (var i = 0; i < segmentProportions.length; i++) {
7447
                        if (segmentProportions[i][1] >= location) {
7448
                            idx = i;
7449
                            // todo is this correct for all connector path types?
7450
                            inSegmentProportion = location == 1 ? 1 : location === 0 ? 0 : (location - segmentProportions[i][0]) / segmentProportionalLengths[i];                    
7451
                            break;
7452
                        }
7453
                    }
7454
                //}
7455
                return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
7456
            },		
7457
            _addSegment = function(conn, type, params) {
7458
                if (params.x1 == params.x2 && params.y1 == params.y2) return;
7459
                var s = new jsPlumb.Segments[type](params);
7460
                segments.push(s);
7461
                totalLength += s.getLength();	
7462
                conn.updateBounds(s);	                                
7463
            },					
7464
            _clearSegments = function() {
7465
                totalLength = 0;
7466
                segments.splice(0, segments.length);
7467
                segmentProportions.splice(0, segmentProportions.length);
7468
                segmentProportionalLengths.splice(0, segmentProportionalLengths.length);
7469
            };
7470
        
7471
        this.setSegments = function(_segs) {
7472
            userProvidedSegments = [];
7473
            totalLength = 0;
7474
            for (var i = 0; i < _segs.length; i++) {      
7475
                userProvidedSegments.push(_segs[i]);
7476
                totalLength += _segs[i].getLength();			            
7477
            }            
7478
        };  
7479
        
7480
        var _prepareCompute = function(params) {
7481
            this.lineWidth = params.lineWidth;
7482
            var segment = jsPlumbGeom.quadrant(params.sourcePos, params.targetPos),
7483
                swapX = params.targetPos[0] < params.sourcePos[0],
7484
                swapY = params.targetPos[1] < params.sourcePos[1],
7485
                lw = params.lineWidth || 1,       
7486
                so = params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint), 
7487
                to = params.targetEndpoint.anchor.getOrientation(params.targetEndpoint),
7488
                x = swapX ? params.targetPos[0] : params.sourcePos[0], 
7489
                y = swapY ? params.targetPos[1] : params.sourcePos[1],
7490
                w = Math.abs(params.targetPos[0] - params.sourcePos[0]),
7491
                h = Math.abs(params.targetPos[1] - params.sourcePos[1]);
7492
            
7493
            // if either anchor does not have an orientation set, we derive one from their relative
7494
            // positions.  we fix the axis to be the one in which the two elements are further apart, and
7495
            // point each anchor at the other element.  this is also used when dragging a new connection.
7496
            if (so[0] === 0 && so[1] === 0 || to[0] === 0 && to[1] === 0) {
7497
                var index = w > h ? 0 : 1, oIndex = [1,0][index];
7498
                so = []; to = [];
7499
                so[index] = params.sourcePos[index] > params.targetPos[index] ? -1 : 1;
7500
                to[index] = params.sourcePos[index] > params.targetPos[index] ? 1 : -1;
7501
                so[oIndex] = 0; to[oIndex] = 0;
7502
            }                    
7503
            
7504
            var sx = swapX ? w + (sourceGap * so[0])  : sourceGap * so[0], 
7505
                sy = swapY ? h + (sourceGap * so[1])  : sourceGap * so[1], 
7506
                tx = swapX ? targetGap * to[0] : w + (targetGap * to[0]),
7507
                ty = swapY ? targetGap * to[1] : h + (targetGap * to[1]),
7508
                oProduct = ((so[0] * to[0]) + (so[1] * to[1]));        
7509
            
7510
            var result = {
7511
                sx:sx, sy:sy, tx:tx, ty:ty, lw:lw, 
7512
                xSpan:Math.abs(tx - sx),
7513
                ySpan:Math.abs(ty - sy),                
7514
                mx:(sx + tx) / 2,
7515
                my:(sy + ty) / 2,                
7516
                so:so, to:to, x:x, y:y, w:w, h:h,
7517
                segment : segment,
7518
                startStubX : sx + (so[0] * sourceStub), 
7519
                startStubY : sy + (so[1] * sourceStub),
7520
                endStubX : tx + (to[0] * targetStub), 
7521
                endStubY : ty + (to[1] * targetStub),
7522
                isXGreaterThanStubTimes2 : Math.abs(sx - tx) > (sourceStub + targetStub),
7523
                isYGreaterThanStubTimes2 : Math.abs(sy - ty) > (sourceStub + targetStub),
7524
                opposite:oProduct == -1,
7525
                perpendicular:oProduct === 0,
7526
                orthogonal:oProduct == 1,
7527
                sourceAxis : so[0] === 0 ? "y" : "x",
7528
                points:[x, y, w, h, sx, sy, tx, ty ]
7529
            };
7530
            result.anchorOrientation = result.opposite ? "opposite" : result.orthogonal ? "orthogonal" : "perpendicular";
7531
            return result;
7532
        };
7533
		
7534
		this.getSegments = function() { return segments; };
7535
7536
        this.updateBounds = function(segment) {
7537
            var segBounds = segment.getBounds();
7538
            this.bounds.minX = Math.min(this.bounds.minX, segBounds.minX);
7539
            this.bounds.maxX = Math.max(this.bounds.maxX, segBounds.maxX);
7540
            this.bounds.minY = Math.min(this.bounds.minY, segBounds.minY);
7541
            this.bounds.maxY = Math.max(this.bounds.maxY, segBounds.maxY);              
7542
        };
7543
        
7544
        var dumpSegmentsToConsole = function() {
7545
            console.log("SEGMENTS:");
7546
            for (var i = 0; i < segments.length; i++) {
7547
                console.log(segments[i].type, segments[i].getLength(), segmentProportions[i]);
7548
            }
7549
        };
7550
		
7551
		this.pointOnPath = function(location, absolute) {
7552
            var seg = _findSegmentForLocation(location, absolute);      
7553
            return seg.segment && seg.segment.pointOnPath(seg.proportion, absolute) || [0,0];
7554
        };
7555
        
7556
        this.gradientAtPoint = function(location) {
7557
            var seg = _findSegmentForLocation(location, absolute);          
7558
            return seg.segment && seg.segment.gradientAtPoint(seg.proportion, absolute) || 0;
7559
        };
7560
        
7561
        this.pointAlongPathFrom = function(location, distance, absolute) {
7562
            var seg = _findSegmentForLocation(location, absolute);
7563
            // TODO what happens if this crosses to the next segment?
7564
            return seg.segment && seg.segment.pointAlongPathFrom(seg.proportion, distance, false) || [0,0];
7565
        };
7566
		
7567
		this.compute = function(params)  {
7568
            if (!edited)
7569
                paintInfo = _prepareCompute.call(this, params);
7570
            
7571
            _clearSegments();
7572
            this._compute(paintInfo, params);
7573
            this.x = paintInfo.points[0];
7574
            this.y = paintInfo.points[1];
7575
            this.w = paintInfo.points[2];
7576
            this.h = paintInfo.points[3];               
7577
            this.segment = paintInfo.segment;         
7578
            _updateSegmentProportions();            
7579
		};
7580
		
7581
		return {
7582
			addSegment:_addSegment,
7583
            prepareCompute:_prepareCompute,
7584
            sourceStub:sourceStub,
7585
            targetStub:targetStub,
7586
            maxStub:Math.max(sourceStub, targetStub),            
7587
            sourceGap:sourceGap,
7588
            targetGap:targetGap,
7589
            maxGap:Math.max(sourceGap, targetGap)
7590
		};		
7591
	};
7592
    jsPlumbUtil.extend(jsPlumb.Connectors.AbstractConnector, AbstractComponent);
7593
	
7594
    /**
7595
     * Class: Connectors.Straight
7596
     * The Straight connector draws a simple straight line between the two anchor points.  It does not have any constructor parameters.
7597
     */
7598
    var Straight = jsPlumb.Connectors.Straight = function() {
7599
    	this.type = "Straight";
7600
		var _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments);		
7601
7602
        this._compute = function(paintInfo, _) {                        
7603
            _super.addSegment(this, "Straight", {x1:paintInfo.sx, y1:paintInfo.sy, x2:paintInfo.startStubX, y2:paintInfo.startStubY});                                                
7604
            _super.addSegment(this, "Straight", {x1:paintInfo.startStubX, y1:paintInfo.startStubY, x2:paintInfo.endStubX, y2:paintInfo.endStubY});                        
7605
            _super.addSegment(this, "Straight", {x1:paintInfo.endStubX, y1:paintInfo.endStubY, x2:paintInfo.tx, y2:paintInfo.ty});                                    
7606
        };                    
7607
    };
7608
    jsPlumbUtil.extend(jsPlumb.Connectors.Straight, jsPlumb.Connectors.AbstractConnector);
7609
    jsPlumb.registerConnectorType(Straight, "Straight");
7610
                    
7611
    /**
7612
     * Class:Connectors.Bezier
7613
     * This Connector draws a Bezier curve with two control points.  You can provide a 'curviness' value which gets applied to jsPlumb's
7614
     * internal voodoo machine and ends up generating locations for the two control points.  See the constructor documentation below.
7615
     */
7616
    /**
7617
     * Function:Constructor
7618
     * 
7619
     * Parameters:
7620
     * 	curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not 
7621
     * actually touch the given point, but it has the tendency to lean towards it.  The larger this value, the greater the curve is pulled from a straight line.
7622
     * Optional; defaults to 150.
7623
     * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0.
7624
     * 
7625
     */
7626
    var Bezier = function(params) {
7627
        params = params || {};
7628
7629
    	var _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
7630
            stub = params.stub || 50,
7631
            majorAnchor = params.curviness || 150,
7632
            minorAnchor = 10;            
7633
7634
        this.type = "Bezier";	
7635
        this.getCurviness = function() { return majorAnchor; };	
7636
        
7637
        this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint) {
7638
        	// determine if the two anchors are perpendicular to each other in their orientation.  we swap the control 
7639
        	// points around if so (code could be tightened up)
7640
        	var soo = sourceEndpoint.anchor.getOrientation(sourceEndpoint), 
7641
        		too = targetEndpoint.anchor.getOrientation(targetEndpoint),
7642
        		perpendicular = soo[0] != too[0] || soo[1] == too[1],
7643
            	p = [];                
7644
            	
7645
            if (!perpendicular) {
7646
                if (soo[0] === 0) // X
7647
                    p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
7648
                else p.push(point[0] - (majorAnchor * soo[0]));
7649
                                 
7650
                if (soo[1] === 0) // Y
7651
                	p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
7652
                else p.push(point[1] + (majorAnchor * too[1]));
7653
            }
7654
             else {
7655
                if (too[0] === 0) // X
7656
                	p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
7657
                else p.push(point[0] + (majorAnchor * too[0]));
7658
                
7659
                if (too[1] === 0) // Y
7660
                	p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
7661
                else p.push(point[1] + (majorAnchor * soo[1]));
7662
             }
7663
7664
            return p;                
7665
        };        
7666
7667
        this._compute = function(paintInfo, p) {                                
7668
			var sp = p.sourcePos,
7669
				tp = p.targetPos,				
7670
                _w = Math.abs(sp[0] - tp[0]),
7671
                _h = Math.abs(sp[1] - tp[1]),            
7672
                _sx = sp[0] < tp[0] ? _w : 0,
7673
                _sy = sp[1] < tp[1] ? _h : 0,
7674
                _tx = sp[0] < tp[0] ? 0 : _w,
7675
                _ty = sp[1] < tp[1] ? 0 : _h,
7676
                _CP = this._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint),
7677
                _CP2 = this._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint);
7678
7679
			_super.addSegment(this, "Bezier", {
7680
				x1:_sx, y1:_sy, x2:_tx, y2:_ty,
7681
				cp1x:_CP[0], cp1y:_CP[1], cp2x:_CP2[0], cp2y:_CP2[1]
7682
			});                    
7683
        };               
7684
    };    
7685
    jsPlumbUtil.extend(Bezier, jsPlumb.Connectors.AbstractConnector);
7686
    jsPlumb.registerConnectorType(Bezier, "Bezier");
7687
    
7688
 // ********************************* END OF CONNECTOR TYPES *******************************************************************
7689
    
7690
 // ********************************* ENDPOINT TYPES *******************************************************************
7691
    
7692
    jsPlumb.Endpoints.AbstractEndpoint = function(params) {
7693
        AbstractComponent.apply(this, arguments);
7694
        var compute = this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {    
7695
            var out = this._compute.apply(this, arguments);
7696
            this.x = out[0];
7697
            this.y = out[1];
7698
            this.w = out[2];
7699
            this.h = out[3];
7700
            this.bounds.minX = this.x;
7701
            this.bounds.minY = this.y;
7702
            this.bounds.maxX = this.x + this.w;
7703
            this.bounds.maxY = this.y + this.h;
7704
            return out;
7705
        };
7706
        return {
7707
            compute:compute,
7708
            cssClass:params.cssClass
7709
        };
7710
    };
7711
    jsPlumbUtil.extend(jsPlumb.Endpoints.AbstractEndpoint, AbstractComponent);
7712
    
7713
    /**
7714
     * Class: Endpoints.Dot
7715
     * A round endpoint, with default radius 10 pixels.
7716
     */    	
7717
    	
7718
	/**
7719
	 * Function: Constructor
7720
	 * 
7721
	 * Parameters:
7722
	 * 
7723
	 * 	radius	-	radius of the endpoint.  defaults to 10 pixels.
7724
	 */
7725
	jsPlumb.Endpoints.Dot = function(params) {        
7726
		this.type = "Dot";
7727
		var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
7728
		params = params || {};				
7729
		this.radius = params.radius || 10;
7730
		this.defaultOffset = 0.5 * this.radius;
7731
		this.defaultInnerRadius = this.radius / 3;			
7732
		
7733
		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
7734
			this.radius = endpointStyle.radius || this.radius;
7735
			var	x = anchorPoint[0] - this.radius,
7736
				y = anchorPoint[1] - this.radius,
7737
                w = this.radius * 2,
7738
                h = this.radius * 2;
7739
7740
            if (endpointStyle.strokeStyle) {
7741
                var lw = endpointStyle.lineWidth || 1;
7742
                x -= lw;
7743
                y -= lw;
7744
                w += (lw * 2);
7745
                h += (lw * 2);
7746
            }
7747
			return [ x, y, w, h, this.radius ];
7748
		};
7749
	};
7750
    jsPlumbUtil.extend(jsPlumb.Endpoints.Dot, jsPlumb.Endpoints.AbstractEndpoint);
7751
	
7752
	/**
7753
	 * Class: Endpoints.Rectangle
7754
	 * A Rectangular Endpoint, with default size 20x20.
7755
	 */
7756
	/**
7757
	 * Function: Constructor
7758
	 * 
7759
	 * Parameters:
7760
	 * 
7761
	 * 	width	- width of the endpoint. defaults to 20 pixels.
7762
	 * 	height	- height of the endpoint. defaults to 20 pixels.	
7763
	 */
7764
	jsPlumb.Endpoints.Rectangle = function(params) {
7765
		this.type = "Rectangle";
7766
		var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
7767
		params = params || {};
7768
		this.width = params.width || 20;
7769
		this.height = params.height || 20;
7770
		
7771
		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
7772
			var width = endpointStyle.width || this.width,
7773
				height = endpointStyle.height || this.height,
7774
				x = anchorPoint[0] - (width/2),
7775
				y = anchorPoint[1] - (height/2);
7776
                
7777
			return [ x, y, width, height];
7778
		};
7779
	};
7780
    jsPlumbUtil.extend(jsPlumb.Endpoints.Rectangle, jsPlumb.Endpoints.AbstractEndpoint);
7781
	
7782
7783
    var DOMElementEndpoint = function(params) {
7784
        jsPlumb.DOMElementComponent.apply(this, arguments);        
7785
        this._jsPlumb.displayElements = [  ];                
7786
    };
7787
    jsPlumbUtil.extend(DOMElementEndpoint, jsPlumb.DOMElementComponent, {
7788
       // jsPlumb.Endpoints.AbstractEndpoint
7789
        getDisplayElements : function() { 
7790
            return this._jsPlumb.displayElements; 
7791
        },        
7792
        appendDisplayElement : function(el) {
7793
            this._jsPlumb.displayElements.push(el);
7794
        }
7795
    });
7796
7797
	/**
7798
	 * Class: Endpoints.Image
7799
	 * Draws an image as the Endpoint.
7800
	 */
7801
	/**
7802
	 * Function: Constructor
7803
	 * 
7804
	 * Parameters:
7805
	 * 
7806
	 * 	src	-	location of the image to use.
7807
7808
    TODO: multiple references to self. not sure quite how to get rid of them entirely. perhaps self = null in the cleanup
7809
    function will suffice
7810
7811
    TODO this class still leaks memory.
7812
7813
	 */
7814
	jsPlumb.Endpoints.Image = function(params) {
7815
				
7816
		this.type = "Image";
7817
		DOMElementEndpoint.apply(this, arguments);
7818
        jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
7819
		
7820
		var _onload = params.onload, 
7821
            src = params.src || params.url,
7822
            parent = params.parent,
7823
            clazz = params.cssClass ? " " + params.cssClass : "";
7824
7825
        this._jsPlumb.img = new Image();		
7826
        this._jsPlumb.ready = false;
7827
        this._jsPlumb.initialized = false;
7828
        this._jsPlumb.deleted = false;
7829
        this._jsPlumb.widthToUse = params.width;
7830
        this._jsPlumb.heightToUse = params.height;
7831
        this._jsPlumb.endpoint = params.endpoint;
7832
7833
		this._jsPlumb.img.onload = function() {
7834
            // check we weren't actually discarded before use (in fact mostly happens in tests)
7835
            if (this._jsPlumb != null) {
7836
    			this._jsPlumb.ready = true;            
7837
    			this._jsPlumb.widthToUse = this._jsPlumb.widthToUse || this._jsPlumb.img.width;
7838
    			this._jsPlumb.heightToUse = this._jsPlumb.heightToUse || this._jsPlumb.img.height;
7839
                if (_onload) {
7840
                    _onload(this);
7841
                }
7842
            }
7843
		}.bind(this);        
7844
7845
        /*
7846
            Function: setImage
7847
            Sets the Image to use in this Endpoint.  
7848
7849
            Parameters:
7850
            img         -   may be a URL or an Image object
7851
            onload      -   optional; a callback to execute once the image has loaded.
7852
        */
7853
        this._jsPlumb.endpoint.setImage = function(_img, onload) {
7854
            var s = _img.constructor == String ? _img : _img.src;
7855
            _onload = onload; 
7856
            this._jsPlumb.img.src = s;
7857
7858
            if (this.canvas != null)
7859
                this.canvas.setAttribute("src", this._jsPlumb.img.src);
7860
        }.bind(this);
7861
7862
        this._jsPlumb.endpoint.setImage(src, _onload);
7863
        /*        
7864
            var s = src.constructor == String ? src : src.src;
7865
            //_onload = onload; 
7866
            this._jsPlumb.img.src = src;
7867
7868
            if (this.canvas != null)
7869
                this.canvas.setAttribute("src", this._jsPlumb.img.src);
7870
       // }.bind(this);
7871
7872
        //this._jsPlumb.endpoint.setImage(src, _onload);*/
7873
7874
		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
7875
			this.anchorPoint = anchorPoint;
7876
			if (this._jsPlumb.ready) return [anchorPoint[0] - this._jsPlumb.widthToUse / 2, anchorPoint[1] - this._jsPlumb.heightToUse / 2, 
7877
									this._jsPlumb.widthToUse, this._jsPlumb.heightToUse];
7878
			else return [0,0,0,0];
7879
		};
7880
		
7881
		this.canvas = document.createElement("img");
7882
		this.canvas.style.margin = 0;
7883
		this.canvas.style.padding = 0;
7884
		this.canvas.style.outline = 0;
7885
		this.canvas.style.position = "absolute";		
7886
		this.canvas.className = this._jsPlumb.instance.endpointClass + clazz;
7887
		if (this._jsPlumb.widthToUse) this.canvas.setAttribute("width", this._jsPlumb.widthToUse);
7888
		if (this._jsPlumb.heightToUse) this.canvas.setAttribute("height", this._jsPlumb.heightToUse);		
7889
		this._jsPlumb.instance.appendElement(this.canvas, parent);
7890
		this.attachListeners(this.canvas, this);		
7891
		
7892
		this.actuallyPaint = function(d, style, anchor) {
7893
			if (!this._jsPlumb.deleted) {
7894
				if (!this._jsPlumb.initialized) {
7895
					this.canvas.setAttribute("src", this._jsPlumb.img.src);
7896
					this.appendDisplayElement(this.canvas);
7897
					this._jsPlumb.initialized = true;
7898
				}
7899
				var x = this.anchorPoint[0] - (this._jsPlumb.widthToUse / 2),
7900
					y = this.anchorPoint[1] - (this._jsPlumb.heightToUse / 2);
7901
				jsPlumbUtil.sizeElement(this.canvas, x, y, this._jsPlumb.widthToUse, this._jsPlumb.heightToUse);
7902
			}
7903
		};
7904
		
7905
		this.paint = function(style, anchor) {
7906
            if (this._jsPlumb != null) {  // may have been deleted
7907
    			if (this._jsPlumb.ready) {
7908
        			this.actuallyPaint(style, anchor);
7909
    			}
7910
    			else { 
7911
    				window.setTimeout(function() {    					
7912
    					this.paint(style, anchor);
7913
    				}.bind(this), 200);
7914
    			}
7915
            }
7916
		};				
7917
	};
7918
    jsPlumbUtil.extend(jsPlumb.Endpoints.Image, [ DOMElementEndpoint, jsPlumb.Endpoints.AbstractEndpoint ], {
7919
        cleanup : function() {            
7920
            this._jsPlumb.deleted = true;
7921
            jsPlumbUtil.removeElement(this.canvas);
7922
            this.canvas = null;
7923
        } 
7924
    });
7925
	
7926
	/*
7927
	 * Class: Endpoints.Blank
7928
	 * An Endpoint that paints nothing (visible) on the screen.  Supports cssClass and hoverClass parameters like all Endpoints.
7929
	 */
7930
	jsPlumb.Endpoints.Blank = function(params) {
7931
		var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
7932
		this.type = "Blank";
7933
		DOMElementEndpoint.apply(this, arguments);		
7934
		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
7935
			return [anchorPoint[0], anchorPoint[1],10,0];
7936
		};
7937
		
7938
		this.canvas = document.createElement("div");
7939
		this.canvas.style.display = "block";
7940
		this.canvas.style.width = "1px";
7941
		this.canvas.style.height = "1px";
7942
		this.canvas.style.background = "transparent";
7943
		this.canvas.style.position = "absolute";
7944
		this.canvas.className = this._jsPlumb.endpointClass;
7945
		jsPlumb.appendElement(this.canvas, params.parent);
7946
		
7947
		this.paint = function(style, anchor) {
7948
			jsPlumbUtil.sizeElement(this.canvas, this.x, this.y, this.w, this.h);	
7949
		};
7950
	};
7951
    jsPlumbUtil.extend(jsPlumb.Endpoints.Blank, [jsPlumb.Endpoints.AbstractEndpoint, DOMElementEndpoint], {
7952
        cleanup:function() {
7953
            if (this.canvas && this.canvas.parentNode) {
7954
                this.canvas.parentNode.removeChild(this.canvas);
7955
            }
7956
        }
7957
    });
7958
	
7959
	/*
7960
	 * Class: Endpoints.Triangle
7961
	 * A triangular Endpoint.  
7962
	 */
7963
	/*
7964
	 * Function: Constructor
7965
	 * 
7966
	 * Parameters:
7967
	 * 
7968
	 * 	width	-	width of the triangle's base.  defaults to 55 pixels.
7969
	 * 	height	-	height of the triangle from base to apex.  defaults to 55 pixels.
7970
	 */
7971
	jsPlumb.Endpoints.Triangle = function(params) {        
7972
		this.type = "Triangle";
7973
        var _super = jsPlumb.Endpoints.AbstractEndpoint.apply(this, arguments);
7974
		params = params || {  };
7975
		params.width = params.width || 55;
7976
		params.height = params.height || 55;
7977
		this.width = params.width;
7978
		this.height = params.height;
7979
		this._compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
7980
			var width = endpointStyle.width || self.width,
7981
			height = endpointStyle.height || self.height,
7982
			x = anchorPoint[0] - (width/2),
7983
			y = anchorPoint[1] - (height/2);
7984
			return [ x, y, width, height ];
7985
		};
7986
	};
7987
// ********************************* END OF ENDPOINT TYPES *******************************************************************
7988
	
7989
7990
// ********************************* OVERLAY DEFINITIONS ***********************************************************************    
7991
7992
	var AbstractOverlay = jsPlumb.Overlays.AbstractOverlay = function(params) {
7993
		this.visible = true;
7994
        this.isAppendedAtTopLevel = true;
7995
		this.component = params.component;
7996
		this.loc = params.location == null ? 0.5 : params.location;
7997
        this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;		
7998
	};
7999
    AbstractOverlay.prototype = {
8000
        cleanup:function() {  
8001
           this.component = null;
8002
           this.canvas = null;
8003
           this.endpointLoc = null;
8004
        },
8005
        setVisible : function(val) { 
8006
            this.visible = val;
8007
            // TODO this is only actually necessary for canvas. so, the Canvas overlay should
8008
            // override setVisible and call this.
8009
            //this.component.repaint();
8010
        },
8011
        isVisible : function() { return this.visible; },
8012
        hide : function() { this.setVisible(false); },
8013
        show : function() { this.setVisible(true); },        
8014
        incrementLocation : function(amount) {
8015
            this.loc += amount;
8016
            this.component.repaint();
8017
        },
8018
        setLocation : function(l) {
8019
            this.loc = l;
8020
            this.component.repaint();
8021
        },
8022
        getLocation : function() {
8023
            return this.loc;
8024
        }
8025
    };
8026
	
8027
	
8028
	/*
8029
	 * Class: Overlays.Arrow
8030
	 * 
8031
	 * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
8032
	 * of the arrow that lines from each tail point converge into.  The foldback point is defined using a decimal that indicates some fraction
8033
	 * of the length of the arrow and has a default value of 0.623.  A foldback point value of 1 would mean that the arrow had a straight line
8034
	 * across the tail.  
8035
	 */
8036
	/*
8037
	 * Function: Constructor
8038
	 * 
8039
	 * Parameters:
8040
	 * 
8041
	 * 	length - distance in pixels from head to tail baseline. default 20.
8042
	 * 	width - width in pixels of the tail baseline. default 20.
8043
	 * 	fillStyle - style to use when filling the arrow.  defaults to "black".
8044
	 * 	strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
8045
	 * 	lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
8046
	 * 	foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to.  defaults to 0.623.
8047
	 * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
8048
	 * 	direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
8049
	 */
8050
	jsPlumb.Overlays.Arrow = function(params) {
8051
		this.type = "Arrow";
8052
		AbstractOverlay.apply(this, arguments);
8053
        this.isAppendedAtTopLevel = false;
8054
		params = params || {};
8055
		var _ju = jsPlumbUtil, _jg = jsPlumbGeom;
8056
		
8057
    	this.length = params.length || 20;
8058
    	this.width = params.width || 20;
8059
    	this.id = params.id;
8060
    	var direction = (params.direction || 1) < 0 ? -1 : 1,
8061
    	    paintStyle = params.paintStyle || { lineWidth:1 },
8062
    	    // how far along the arrow the lines folding back in come to. default is 62.3%.
8063
    	    foldback = params.foldback || 0.623;
8064
    	    	
8065
    	this.computeMaxSize = function() { return self.width * 1.5; };    	
8066
    	//this.cleanup = function() { };  // nothing to clean up for Arrows    
8067
    	this.draw = function(component, currentConnectionPaintStyle) {
8068
8069
            var hxy, mid, txy, tail, cxy;
8070
            if (component.pointAlongPathFrom) {
8071
8072
                if (_ju.isString(this.loc) || this.loc > 1 || this.loc < 0) {                    
8073
                    var l = parseInt(this.loc, 10),
8074
                        fromLoc = this.loc < 0 ? 1 : 0;
8075
                    hxy = component.pointAlongPathFrom(fromLoc, l, false);
8076
                    mid = component.pointAlongPathFrom(fromLoc, l - (direction * this.length / 2), false);
8077
                    txy = _jg.pointOnLine(hxy, mid, this.length);
8078
                }
8079
                else if (this.loc == 1) {                
8080
					hxy = component.pointOnPath(this.loc);					           
8081
                    mid = component.pointAlongPathFrom(this.loc, -(this.length));
8082
					txy = _jg.pointOnLine(hxy, mid, this.length);
8083
					
8084
					if (direction == -1) {
8085
						var _ = txy;
8086
						txy = hxy;
8087
						hxy = _;
8088
					}
8089
                }
8090
                else if (this.loc === 0) {					                    
8091
					txy = component.pointOnPath(this.loc);                    
8092
					mid = component.pointAlongPathFrom(this.loc, this.length);                    
8093
					hxy = _jg.pointOnLine(txy, mid, this.length);                    
8094
					if (direction == -1) {
8095
						var __ = txy;
8096
						txy = hxy;
8097
						hxy = __;
8098
					}
8099
                }
8100
                else {                    
8101
    			    hxy = component.pointAlongPathFrom(this.loc, direction * this.length / 2);
8102
                    mid = component.pointOnPath(this.loc);
8103
                    txy = _jg.pointOnLine(hxy, mid, this.length);
8104
                }
8105
8106
                tail = _jg.perpendicularLineTo(hxy, txy, this.width);
8107
                cxy = _jg.pointOnLine(hxy, txy, foldback * this.length);    			
8108
    			
8109
    			var d = { hxy:hxy, tail:tail, cxy:cxy },
8110
    			    strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
8111
    			    fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
8112
    			    lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth,
8113
                    info = {
8114
                        component:component, 
8115
                        d:d, 
8116
                        lineWidth:lineWidth, 
8117
                        strokeStyle:strokeStyle, 
8118
                        fillStyle:fillStyle,
8119
                        minX:Math.min(hxy.x, tail[0].x, tail[1].x),
8120
                        maxX:Math.max(hxy.x, tail[0].x, tail[1].x),
8121
                        minY:Math.min(hxy.y, tail[0].y, tail[1].y),
8122
                        maxY:Math.max(hxy.y, tail[0].y, tail[1].y)
8123
                    };    			
8124
						    
8125
                return info;
8126
            }
8127
            else return {component:component, minX:0,maxX:0,minY:0,maxY:0};
8128
    	};
8129
    };    
8130
    jsPlumbUtil.extend(jsPlumb.Overlays.Arrow, AbstractOverlay);      
8131
    
8132
    /*
8133
     * Class: Overlays.PlainArrow
8134
	 * 
8135
	 * A basic arrow.  This is in fact just one instance of the more generic case in which the tail folds back on itself to some
8136
	 * point along the length of the arrow: in this case, that foldback point is the full length of the arrow.  so it just does
8137
	 * a 'call' to Arrow with foldback set appropriately.       
8138
	 */
8139
    /*
8140
     * Function: Constructor
8141
     * See <Overlays.Arrow> for allowed parameters for this overlay.
8142
     */
8143
    jsPlumb.Overlays.PlainArrow = function(params) {
8144
    	params = params || {};    	
8145
    	var p = jsPlumb.extend(params, {foldback:1});
8146
    	jsPlumb.Overlays.Arrow.call(this, p);
8147
    	this.type = "PlainArrow";
8148
    };
8149
    jsPlumbUtil.extend(jsPlumb.Overlays.PlainArrow, jsPlumb.Overlays.Arrow);
8150
        
8151
    /*
8152
     * Class: Overlays.Diamond
8153
     * 
8154
	 * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
8155
	 * happens that in this case, that point is greater than the length of the the arrow.    
8156
	 * 
8157
	 *      this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
8158
	 *      center is actually 1/4 of the way along for this guy.  but we don't have any knowledge of pixels at this point, so we're kind of
8159
	 *      stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
8160
	 *      would be -l/4 in this case - move along one quarter of the total length.
8161
	 */
8162
    /*
8163
     * Function: Constructor
8164
     * See <Overlays.Arrow> for allowed parameters for this overlay.
8165
     */
8166
    jsPlumb.Overlays.Diamond = function(params) {
8167
    	params = params || {};    	
8168
    	var l = params.length || 40,
8169
    	    p = jsPlumb.extend(params, {length:l/2, foldback:2});
8170
    	jsPlumb.Overlays.Arrow.call(this, p);
8171
    	this.type = "Diamond";
8172
    };
8173
    jsPlumbUtil.extend(jsPlumb.Overlays.Diamond, jsPlumb.Overlays.Arrow);
8174
8175
    var _getDimensions = function(component) {
8176
        if (component._jsPlumb.cachedDimensions == null)
8177
            component._jsPlumb.cachedDimensions = component.getDimensions();
8178
        return component._jsPlumb.cachedDimensions;
8179
    };      
8180
	
8181
	// abstract superclass for overlays that add an element to the DOM.
8182
    var AbstractDOMOverlay = function(params) {
8183
		jsPlumb.DOMElementComponent.apply(this, arguments);
8184
    	AbstractOverlay.apply(this, arguments);
8185
		
8186
		var jpcl = jsPlumb.CurrentLibrary;		
8187
		this.id = params.id;
8188
        this._jsPlumb.div = null;		
8189
        this._jsPlumb.initialised = false;
8190
        this._jsPlumb.component = params.component;
8191
        this._jsPlumb.cachedDimensions = null;
8192
        this._jsPlumb.create = params.create;
8193
		
8194
		this.getElement = function() {
8195
			if (this._jsPlumb.div == null) {
8196
                var div = this._jsPlumb.div = jpcl.getDOMElement(this._jsPlumb.create(this._jsPlumb.component));                
8197
                div.style.position   =   "absolute";     
8198
                var clazz = params._jsPlumb.overlayClass + " " + 
8199
                    (this.cssClass ? this.cssClass : 
8200
                    params.cssClass ? params.cssClass : "");        
8201
                div.className = clazz;
8202
                this._jsPlumb.instance.appendElement(div, this._jsPlumb.component.parent);
8203
                this._jsPlumb.instance.getId(div);      
8204
                this.attachListeners(div, this);
8205
                this.canvas = div;
8206
			}
8207
    		return this._jsPlumb.div;
8208
    	};
8209
				
8210
		this.draw = function(component, currentConnectionPaintStyle, absolutePosition) {
8211
	    	var td = _getDimensions(this);
8212
	    	if (td != null && td.length == 2) {
8213
				var cxy = { x:0,y:0 };
8214
8215
                // absolutePosition would have been set by a call to connection.setAbsoluteOverlayPosition.
8216
                if (absolutePosition) {
8217
                    cxy = { x:absolutePosition[0], y:absolutePosition[1] };
8218
                }
8219
                else if (component.pointOnPath) {
8220
                    var loc = this.loc, absolute = false;
8221
                    if (jsPlumbUtil.isString(this.loc) || this.loc < 0 || this.loc > 1) {
8222
                        loc = parseInt(this.loc, 10);
8223
                        absolute = true;
8224
                    }
8225
                    cxy = component.pointOnPath(loc, absolute);  // a connection
8226
                }
8227
                else {
8228
                    var locToUse = this.loc.constructor == Array ? this.loc : this.endpointLoc;
8229
                    cxy = { x:locToUse[0] * component.w,
8230
                            y:locToUse[1] * component.h };      
8231
                } 
8232
                           
8233
				var minx = cxy.x - (td[0] / 2),
8234
				    miny = cxy.y - (td[1] / 2);
8235
8236
                return {
8237
                    component:component, 
8238
                    d:{ minx:minx, miny:miny, td:td, cxy:cxy },
8239
                    minX:minx, 
8240
                    maxX:minx + td[0], 
8241
                    minY:miny, 
8242
                    maxY:miny + td[1]
8243
                };								
8244
        	}
8245
	    	else return {minX:0,maxX:0,minY:0,maxY:0};
8246
	    };	   	   		
8247
	};
8248
    jsPlumbUtil.extend(AbstractDOMOverlay, [jsPlumb.DOMElementComponent, AbstractOverlay], {
8249
        getDimensions : function() {            
8250
            return jsPlumb.CurrentLibrary.getSize(jsPlumb.CurrentLibrary.getElementObject(this.getElement()));            
8251
        },
8252
        setVisible : function(state) {
8253
            this._jsPlumb.div.style.display = state ? "block" : "none";
8254
        },
8255
        /*
8256
         * Function: clearCachedDimensions
8257
         * Clears the cached dimensions for the label. As a performance enhancement, label dimensions are
8258
         * cached from 1.3.12 onwards. The cache is cleared when you change the label text, of course, but
8259
         * there are other reasons why the text dimensions might change - if you make a change through CSS, for
8260
         * example, you might change the font size.  in that case you should explicitly call this method.
8261
         */
8262
        clearCachedDimensions : function() {
8263
            this._jsPlumb.cachedDimensions = null;
8264
        },
8265
        cleanup : function() {
8266
            if (this._jsPlumb.div != null) 
8267
                jsPlumb.CurrentLibrary.removeElement(this._jsPlumb.div);
8268
        },
8269
        computeMaxSize : function() {
8270
            var td = _getDimensions(this);
8271
            return Math.max(td[0], td[1]);
8272
        },
8273
        reattachListeners : function(connector) {
8274
            if (this._jsPlumb.div) {
8275
                this.reattachListenersForElement(this._jsPlumb.div, this, connector);
8276
            }
8277
        },
8278
        paint : function(p, containerExtents) {
8279
            if (!this._jsPlumb.initialised) {
8280
                this.getElement();
8281
                p.component.appendDisplayElement(this._jsPlumb.div);
8282
                this.attachListeners(this._jsPlumb.div, p.component);
8283
                this._jsPlumb.initialised = true;
8284
            }
8285
            this._jsPlumb.div.style.left = (p.component.x + p.d.minx) + "px";
8286
            this._jsPlumb.div.style.top = (p.component.y + p.d.miny) + "px";            
8287
        }
8288
    });
8289
	
8290
	/*
8291
     * Class: Overlays.Custom
8292
     * A Custom overlay. You supply a 'create' function which returns some DOM element, and jsPlumb positions it.
8293
     * The 'create' function is passed a Connection or Endpoint.
8294
     */
8295
    /*
8296
     * Function: Constructor
8297
     * 
8298
     * Parameters:
8299
     * 	create - function for jsPlumb to call that returns a DOM element.
8300
     * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
8301
     * 	id - optional id to use for later retrieval of this overlay.
8302
     * 	
8303
     */
8304
    jsPlumb.Overlays.Custom = function(params) {
8305
    	this.type = "Custom";    	
8306
    	AbstractDOMOverlay.apply(this, arguments);		    	        		    	    		
8307
    };
8308
    jsPlumbUtil.extend(jsPlumb.Overlays.Custom, AbstractDOMOverlay);
8309
8310
    jsPlumb.Overlays.GuideLines = function() {
8311
        var self = this;
8312
        self.length = 50;
8313
        self.lineWidth = 5;
8314
        this.type = "GuideLines";
8315
        AbstractOverlay.apply(this, arguments);
8316
        jsPlumb.jsPlumbUIComponent.apply(this, arguments);
8317
        this.draw = function(connector, currentConnectionPaintStyle) {
8318
8319
            var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
8320
                mid = connector.pointOnPath(self.loc),
8321
                tail = jsPlumbGeom.pointOnLine(head, mid, self.length),
8322
                tailLine = jsPlumbGeom.perpendicularLineTo(head, tail, 40),
8323
                headLine = jsPlumbGeom.perpendicularLineTo(tail, head, 20);
8324
8325
            return {
8326
                connector:connector,
8327
                head:head,
8328
                tail:tail,
8329
                headLine:headLine,
8330
                tailLine:tailLine,                
8331
                minX:Math.min(head.x, tail.x, headLine[0].x, headLine[1].x), 
8332
                minY:Math.min(head.y, tail.y, headLine[0].y, headLine[1].y), 
8333
                maxX:Math.max(head.x, tail.x, headLine[0].x, headLine[1].x), 
8334
                maxY:Math.max(head.y, tail.y, headLine[0].y, headLine[1].y)
8335
            };
8336
        };
8337
8338
       // this.cleanup = function() { };  // nothing to clean up for GuideLines
8339
    };
8340
    
8341
    /*
8342
     * Class: Overlays.Label
8343
     
8344
     */
8345
    /*
8346
     * Function: Constructor
8347
     * 
8348
     * Parameters:
8349
     * 	cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
8350
     *             defined.  This parameter is preferred to using labelStyle, borderWidth and borderStyle.
8351
     * 	label - the label to paint.  May be a string or a function that returns a string.  Nothing will be painted if your label is null or your
8352
     *         label function returns null.  empty strings _will_ be painted.
8353
     * 	location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
8354
     * 	id - optional id to use for later retrieval of this overlay.
8355
     * 
8356
     * 	
8357
     */
8358
    jsPlumb.Overlays.Label =  function(params) {		   
8359
		this.labelStyle = params.labelStyle;
8360
        
8361
        var labelWidth = null, labelHeight =  null, labelText = null, labelPadding = null;
8362
		this.cssClass = this.labelStyle != null ? this.labelStyle.cssClass : null;
8363
		var p = jsPlumb.extend({
8364
            create : function() {
8365
                return document.createElement("div");
8366
            }}, params);
8367
    	jsPlumb.Overlays.Custom.call(this, p);
8368
		this.type = "Label";    	
8369
        this.label = params.label || "";
8370
        this.labelText = null;
8371
        if (this.labelStyle) {
8372
            var el = this.getElement();            
8373
            this.labelStyle.font = this.labelStyle.font || "12px sans-serif";
8374
            el.style.font = this.labelStyle.font;
8375
            el.style.color = this.labelStyle.color || "black";
8376
            if (this.labelStyle.fillStyle) el.style.background = this.labelStyle.fillStyle;
8377
            if (this.labelStyle.borderWidth > 0) {
8378
                var dStyle = this.labelStyle.borderStyle ? this.labelStyle.borderStyle : "black";
8379
                el.style.border = this.labelStyle.borderWidth  + "px solid " + dStyle;
8380
            }
8381
            if (this.labelStyle.padding) el.style.padding = this.labelStyle.padding;            
8382
        }
8383
8384
    };
8385
    jsPlumbUtil.extend(jsPlumb.Overlays.Label, jsPlumb.Overlays.Custom, {
8386
        cleanup:function() {
8387
            this.div = null;
8388
            this.label = null;
8389
            this.labelText = null;
8390
            this.cssClass = null;
8391
            this.labelStyle = null;
8392
        },
8393
        getLabel : function() {
8394
            return this.label;
8395
        },
8396
        /*
8397
         * Function: setLabel
8398
         * sets the label's, um, label.  you would think i'd call this function
8399
         * 'setText', but you can pass either a Function or a String to this, so
8400
         * it makes more sense as 'setLabel'. This uses innerHTML on the label div, so keep
8401
         * that in mind if you need escaped HTML.
8402
         */
8403
        setLabel : function(l) {
8404
            this.label = l;
8405
            this.labelText = null;
8406
            this.clearCachedDimensions();
8407
            this.update();
8408
            this.component.repaint();
8409
        },
8410
        getDimensions : function() {                
8411
            this.update();
8412
            return AbstractDOMOverlay.prototype.getDimensions.apply(this, arguments);
8413
        },
8414
        update : function() {
8415
            if (typeof this.label == "function") {
8416
                var lt = this.label(this);
8417
                this.getElement().innerHTML = lt.replace(/\r\n/g, "<br/>");
8418
            }
8419
            else {
8420
                if (this.labelText == null) {
8421
                    this.labelText = this.label;
8422
                    this.getElement().innerHTML = this.labelText.replace(/\r\n/g, "<br/>");
8423
                }
8424
            }
8425
        }
8426
    });		
8427
8428
 // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
8429
    
8430
})();
8431
/*
8432
 * jsPlumb
8433
 * 
8434
 * Title:jsPlumb 1.5.5
8435
 * 
8436
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
8437
 * elements, or VML.  
8438
 * 
8439
 * This file contains the 'flowchart' connectors, consisting of vertical and horizontal line segments.
8440
 *
8441
 * Copyright (c) 2010 - 2013 Simon Porritt ([email protected])
8442
 * 
8443
 * http://jsplumb.org
8444
 * http://github.com/sporritt/jsplumb
8445
 * http://code.google.com/p/jsplumb
8446
 * 
8447
 * Dual licensed under the MIT and GPL2 licenses.
8448
 */
8449
;(function() {
8450
   
8451
    /**
8452
     * Function: Constructor
8453
     * 
8454
     * Parameters:
8455
     * 	stub - minimum length for the stub at each end of the connector. This can be an integer, giving a value for both ends of the connections, 
8456
     * or an array of two integers, giving separate values for each end. The default is an integer with value 30 (pixels). 
8457
     *  gap  - gap to leave between the end of the connector and the element on which the endpoint resides. if you make this larger than stub then you will see some odd looking behaviour.  
8458
                Like stub, this can be an array or a single value. defaults to 0 pixels for each end.     
8459
     * cornerRadius - optional, defines the radius of corners between segments. defaults to 0 (hard edged corners).
8460
     * alwaysRespectStubs - defaults to false. whether or not the connectors should always draw the stub, or, if the two elements
8461
                            are in close proximity to each other (closer than the sum of the two stubs), to adjust the stubs.
8462
     */
8463
    var Flowchart = function(params) {
8464
        this.type = "Flowchart";
8465
        params = params || {};
8466
        params.stub = params.stub == null ? 30 : params.stub;
8467
        var self = this,
8468
            _super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),		
8469
            midpoint = params.midpoint == null ? 0.5 : params.midpoint,
8470
            points = [], segments = [],
8471
            grid = params.grid,
8472
            alwaysRespectStubs = params.alwaysRespectStubs,
8473
            userSuppliedSegments = null,
8474
            lastx = null, lasty = null, lastOrientation,	
8475
            cornerRadius = params.cornerRadius != null ? params.cornerRadius : 0,	
8476
            sgn = function(n) { return n < 0 ? -1 : n === 0 ? 0 : 1; },            
8477
            /**
8478
             * helper method to add a segment.
8479
             */
8480
            addSegment = function(segments, x, y, paintInfo) {
8481
                if (lastx == x && lasty == y) return;
8482
                var lx = lastx == null ? paintInfo.sx : lastx,
8483
                    ly = lasty == null ? paintInfo.sy : lasty,
8484
                    o = lx == x ? "v" : "h",
8485
                    sgnx = sgn(x - lx),
8486
                    sgny = sgn(y - ly);
8487
                    
8488
                lastx = x;
8489
                lasty = y;				    		                
8490
                segments.push([lx, ly, x, y, o, sgnx, sgny]);
8491
            },
8492
            segLength = function(s) {
8493
                return Math.sqrt(Math.pow(s[0] - s[2], 2) + Math.pow(s[1] - s[3], 2));    
8494
            },
8495
            _cloneArray = function(a) { var _a = []; _a.push.apply(_a, a); return _a;},
8496
            updateMinMax = function(a1) {
8497
                self.bounds.minX = Math.min(self.bounds.minX, a1[2]);
8498
                self.bounds.maxX = Math.max(self.bounds.maxX, a1[2]);
8499
                self.bounds.minY = Math.min(self.bounds.minY, a1[3]);
8500
                self.bounds.maxY = Math.max(self.bounds.maxY, a1[3]);    
8501
            },
8502
            writeSegments = function(conn, segments, paintInfo) {
8503
                var current, next;                
8504
                for (var i = 0; i < segments.length - 1; i++) {
8505
                    
8506
                    current = current || _cloneArray(segments[i]);
8507
                    next = _cloneArray(segments[i + 1]);
8508
                    if (cornerRadius > 0 && current[4] != next[4]) {
8509
                        var radiusToUse = Math.min(cornerRadius, segLength(current), segLength(next));
8510
                        // right angle. adjust current segment's end point, and next segment's start point.
8511
                        current[2] -= current[5] * radiusToUse;
8512
                        current[3] -= current[6] * radiusToUse;
8513
                        next[0] += next[5] * radiusToUse;
8514
                        next[1] += next[6] * radiusToUse;														                         			
8515
                        var ac = (current[6] == next[5] && next[5] == 1) ||
8516
                                 ((current[6] == next[5] && next[5] === 0) && current[5] != next[6]) ||
8517
                                 (current[6] == next[5] && next[5] == -1),
8518
                            sgny = next[1] > current[3] ? 1 : -1,
8519
                            sgnx = next[0] > current[2] ? 1 : -1,
8520
                            sgnEqual = sgny == sgnx,
8521
                            cx = (sgnEqual && ac || (!sgnEqual && !ac)) ? next[0] : current[2],
8522
                            cy = (sgnEqual && ac || (!sgnEqual && !ac)) ? current[3] : next[1];                                                        
8523
                        
8524
                        _super.addSegment(conn, "Straight", {
8525
                            x1:current[0], y1:current[1], x2:current[2], y2:current[3]
8526
                        });
8527
                            
8528
                        _super.addSegment(conn, "Arc", {
8529
                            r:radiusToUse, 
8530
                            x1:current[2], 
8531
                            y1:current[3], 
8532
                            x2:next[0], 
8533
                            y2:next[1],
8534
                            cx:cx,
8535
                            cy:cy,
8536
                            ac:ac
8537
                        });	                                            
8538
                    }
8539
                    else {                 
8540
                        // dx + dy are used to adjust for line width.
8541
                        var dx = (current[2] == current[0]) ? 0 : (current[2] > current[0]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2),
8542
                            dy = (current[3] == current[1]) ? 0 : (current[3] > current[1]) ? (paintInfo.lw / 2) : -(paintInfo.lw / 2);
8543
                        _super.addSegment(conn, "Straight", {
8544
                            x1:current[0]- dx, y1:current[1]-dy, x2:current[2] + dx, y2:current[3] + dy
8545
                        });
8546
                    }                    
8547
                    current = next;
8548
                }
8549
                // last segment
8550
                _super.addSegment(conn, "Straight", {
8551
                    x1:next[0], y1:next[1], x2:next[2], y2:next[3]
8552
                });                             
8553
            };
8554
        
8555
        this.setSegments = function(s) {
8556
            userSuppliedSegments = s;
8557
        };
8558
        
8559
        this.isEditable = function() { return true; };
8560
        
8561
        /*
8562
            Function: getOriginalSegments
8563
            Gets the segments before the addition of rounded corners. This is used by the flowchart
8564
            connector editor, since it only wants to concern itself with the original segments.
8565
        */
8566
        this.getOriginalSegments = function() {
8567
            return userSuppliedSegments || segments;
8568
        };
8569
        
8570
        this._compute = function(paintInfo, params) {
8571
            
8572
            if (params.clearEdits)
8573
                userSuppliedSegments = null;
8574
            
8575
            if (userSuppliedSegments != null) {
8576
                writeSegments(this, userSuppliedSegments, paintInfo);                
8577
                return;
8578
            }
8579
            
8580
            segments = [];
8581
            lastx = null; lasty = null;
8582
            lastOrientation = null;          
8583
            
8584
            var midx = paintInfo.startStubX + ((paintInfo.endStubX - paintInfo.startStubX) * midpoint),
8585
                midy = paintInfo.startStubY + ((paintInfo.endStubY - paintInfo.startStubY) * midpoint);                                                                                                    
8586
    
8587
            var findClearedLine = function(start, mult, anchorPos, dimension) {
8588
                    return start + (mult * (( 1 - anchorPos) * dimension) + _super.maxStub);
8589
                },
8590
                orientations = { x:[ 0, 1 ], y:[ 1, 0 ] },
8591
                commonStubCalculator = function(axis) {
8592
                    return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ];                    
8593
                },
8594
                stubCalculators = {
8595
                    perpendicular:commonStubCalculator,
8596
                    orthogonal:commonStubCalculator,
8597
                    opposite:function(axis) {  
8598
                        var pi = paintInfo,
8599
                            idx = axis == "x" ? 0 : 1, 
8600
                            areInProximity = {
8601
                                "x":function() {                                    
8602
                                    return ( (pi.so[idx] == 1 && ( 
8603
                                        ( (pi.startStubX > pi.endStubX) && (pi.tx > pi.startStubX) ) ||
8604
                                        ( (pi.sx > pi.endStubX) && (pi.tx > pi.sx))))) ||
8605
8606
                                        ( (pi.so[idx] == -1 && ( 
8607
                                            ( (pi.startStubX < pi.endStubX) && (pi.tx < pi.startStubX) ) ||
8608
                                            ( (pi.sx < pi.endStubX) && (pi.tx < pi.sx)))));
8609
                                },
8610
                                "y":function() {                                     
8611
                                    return ( (pi.so[idx] == 1 && ( 
8612
                                        ( (pi.startStubY > pi.endStubY) && (pi.ty > pi.startStubY) ) ||
8613
                                        ( (pi.sy > pi.endStubY) && (pi.ty > pi.sy))))) ||
8614
8615
                                        ( (pi.so[idx] == -1 && ( 
8616
                                        ( (pi.startStubY < pi.endStubY) && (pi.ty < pi.startStubY) ) ||
8617
                                        ( (pi.sy < pi.endStubY) && (pi.ty < pi.sy)))));
8618
                                }
8619
                            };
8620
8621
                        if (!alwaysRespectStubs && areInProximity[axis]()) {                   
8622
                            return {
8623
                                "x":[(paintInfo.sx + paintInfo.tx) / 2, paintInfo.startStubY, (paintInfo.sx + paintInfo.tx) / 2, paintInfo.endStubY],
8624
                                "y":[paintInfo.startStubX, (paintInfo.sy + paintInfo.ty) / 2, paintInfo.endStubX, (paintInfo.sy + paintInfo.ty) / 2]
8625
                            }[axis];
8626
                        }
8627
                        else {
8628
                            return [ paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY ];   
8629
                        }
8630
                    }
8631
                },
8632
                lineCalculators = {
8633
                    perpendicular : function(axis, ss, oss, es, oes) {
8634
                        var pi = paintInfo, 
8635
                            sis = {
8636
                                x:[ [ [ 1,2,3,4 ], null, [ 2,1,4,3 ] ], null, [ [ 4,3,2,1 ], null, [ 3,4,1,2 ] ] ],
8637
                                y:[ [ [ 3,2,1,4 ], null, [ 2,3,4,1 ] ], null, [ [ 4,1,2,3 ], null, [ 1,4,3,2 ] ] ]
8638
                            },
8639
                            stubs = { 
8640
                                x:[ [ pi.startStubX, pi.endStubX ] , null, [ pi.endStubX, pi.startStubX ] ],
8641
                                y:[ [ pi.startStubY, pi.endStubY ] , null, [ pi.endStubY, pi.startStubY ] ]
8642
                            },
8643
                            midLines = {
8644
                                x:[ [ midx, pi.startStubY ], [ midx, pi.endStubY ] ],
8645
                                y:[ [ pi.startStubX, midy ], [ pi.endStubX, midy ] ]
8646
                            },
8647
                            linesToEnd = {
8648
                                x:[ [ pi.endStubX, pi.startStubY ] ],
8649
                                y:[ [ pi.startStubX, pi.endStubY ] ]
8650
                            },
8651
                            startToEnd = {
8652
                                x:[ [ pi.startStubX, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ],        
8653
                                y:[ [ pi.endStubX, pi.startStubY ], [ pi.endStubX, pi.endStubY ] ]
8654
                            },
8655
                            startToMidToEnd = {
8656
                                x:[ [ pi.startStubX, midy ], [ pi.endStubX, midy ], [ pi.endStubX, pi.endStubY ] ],
8657
                                y:[ [ midx, pi.startStubY ], [ midx, pi.endStubY ], [ pi.endStubX, pi.endStubY ] ]
8658
                            },
8659
                            otherStubs = {
8660
                                x:[ pi.startStubY, pi.endStubY ],
8661
                                y:[ pi.startStubX, pi.endStubX ]                                    
8662
                            },
8663
                            soIdx = orientations[axis][0], toIdx = orientations[axis][1],
8664
                            _so = pi.so[soIdx] + 1,
8665
                            _to = pi.to[toIdx] + 1,
8666
                            otherFlipped = (pi.to[toIdx] == -1 && (otherStubs[axis][1] < otherStubs[axis][0])) || (pi.to[toIdx] == 1 && (otherStubs[axis][1] > otherStubs[axis][0])),
8667
                            stub1 = stubs[axis][_so][0],
8668
                            stub2 = stubs[axis][_so][1],
8669
                            segmentIndexes = sis[axis][_so][_to];
8670
8671
                        if (pi.segment == segmentIndexes[3] || (pi.segment == segmentIndexes[2] && otherFlipped)) {
8672
                            return midLines[axis];       
8673
                        }
8674
                        else if (pi.segment == segmentIndexes[2] && stub2 < stub1) {
8675
                            return linesToEnd[axis];
8676
                        }
8677
                        else if ((pi.segment == segmentIndexes[2] && stub2 >= stub1) || (pi.segment == segmentIndexes[1] && !otherFlipped)) {
8678
                            return startToMidToEnd[axis];
8679
                        }
8680
                        else if (pi.segment == segmentIndexes[0] || (pi.segment == segmentIndexes[1] && otherFlipped)) {
8681
                            return startToEnd[axis];  
8682
                        }                                
8683
                    },
8684
                    orthogonal : function(axis, startStub, otherStartStub, endStub, otherEndStub) {                    
8685
                        var pi = paintInfo,                                            
8686
                            extent = {
8687
                                "x":pi.so[0] == -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub),
8688
                                "y":pi.so[1] == -1 ? Math.min(startStub, endStub) : Math.max(startStub, endStub)
8689
                            }[axis];
8690
                                                
8691
                        return {
8692
                            "x":[ [ extent, otherStartStub ],[ extent, otherEndStub ], [ endStub, otherEndStub ] ],
8693
                            "y":[ [ otherStartStub, extent ], [ otherEndStub, extent ], [ otherEndStub, endStub ] ]
8694
                        }[axis];                    
8695
                    },
8696
                    opposite : function(axis, ss, oss, es, oes) {                                                
8697
                        var pi = paintInfo,
8698
                            otherAxis = {"x":"y","y":"x"}[axis], 
8699
                            dim = {"x":"height","y":"width"}[axis],
8700
                            comparator = pi["is" + axis.toUpperCase() + "GreaterThanStubTimes2"];
8701
8702
                        if (params.sourceEndpoint.elementId == params.targetEndpoint.elementId) {
8703
                            var _val = oss + ((1 - params.sourceEndpoint.anchor[otherAxis]) * params.sourceInfo[dim]) + _super.maxStub;
8704
                            return {
8705
                                "x":[ [ ss, _val ], [ es, _val ] ],
8706
                                "y":[ [ _val, ss ], [ _val, es ] ]
8707
                            }[axis];
8708
                            
8709
                        }                                                        
8710
                        else if (!comparator || (pi.so[idx] == 1 && ss > es) || (pi.so[idx] == -1 && ss < es)) {                                            
8711
                            return {
8712
                                "x":[[ ss, midy ], [ es, midy ]],
8713
                                "y":[[ midx, ss ], [ midx, es ]]
8714
                            }[axis];
8715
                        }
8716
                        else if ((pi.so[idx] == 1 && ss < es) || (pi.so[idx] == -1 && ss > es)) {
8717
                            return {
8718
                                "x":[[ midx, pi.sy ], [ midx, pi.ty ]],
8719
                                "y":[[ pi.sx, midy ], [ pi.tx, midy ]]
8720
                            }[axis];
8721
                        }                        
8722
                    }
8723
                };
8724
8725
            var stubs = stubCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis),
8726
                idx = paintInfo.sourceAxis == "x" ? 0 : 1,
8727
                oidx = paintInfo.sourceAxis == "x" ? 1 : 0,                            
8728
                ss = stubs[idx],
8729
                oss = stubs[oidx],
8730
                es = stubs[idx + 2],
8731
                oes = stubs[oidx + 2];
8732
8733
            // add the start stub segment.
8734
            addSegment(segments, stubs[0], stubs[1], paintInfo);           
8735
8736
            // compute the rest of the line
8737
            var p = lineCalculators[paintInfo.anchorOrientation](paintInfo.sourceAxis, ss, oss, es, oes);            
8738
            if (p) {
8739
                for (var i = 0; i < p.length; i++) {                	
8740
                    addSegment(segments, p[i][0], p[i][1], paintInfo);
8741
                }
8742
            }          
8743
            
8744
            // line to end stub
8745
            addSegment(segments, stubs[2], stubs[3], paintInfo);
8746
    
8747
            // end stub to end
8748
            addSegment(segments, paintInfo.tx, paintInfo.ty, paintInfo);               
8749
            
8750
            writeSegments(this, segments, paintInfo);                            
8751
        };	
8752
8753
        this.getPath = function() {
8754
            var _last = null, _lastAxis = null, s = [], segs = userSuppliedSegments || segments;
8755
            for (var i = 0; i < segs.length; i++) {
8756
                var seg = segs[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2);
8757
                if (_last != null && _lastAxis === axis) {
8758
                    _last[axisIndex] = seg[axisIndex];                            
8759
                }
8760
                else {
8761
                    if (seg[0] != seg[2] || seg[1] != seg[3]) {
8762
                        s.push({
8763
                            start:[ seg[0], seg[1] ],
8764
                            end:[ seg[2], seg[3] ]
8765
                        });                    
8766
                        _last = seg;
8767
                        _lastAxis = seg[4];
8768
                    }
8769
                }
8770
            }
8771
            return s;
8772
        };	
8773
8774
        this.setPath = function(path) {
8775
            userSuppliedSegments = [];
8776
            for (var i = 0; i < path.length; i++) {
8777
                 var lx = path[i].start[0],
8778
                    ly = path[i].start[1],
8779
                    x = path[i].end[0],
8780
                    y = path[i].end[1],
8781
                    o = lx == x ? "v" : "h",
8782
                    sgnx = sgn(x - lx),
8783
                    sgny = sgn(y - ly);
8784
8785
                userSuppliedSegments.push([lx, ly, x, y, o, sgnx, sgny]);
8786
            }
8787
        };
8788
    };
8789
8790
    jsPlumbUtil.extend(Flowchart, jsPlumb.Connectors.AbstractConnector);
8791
    jsPlumb.registerConnectorType(Flowchart, "Flowchart");
8792
})();
8793
/*
8794
 * jsPlumb
8795
 *
8796
 * Title:jsPlumb 1.5.5
8797
 *
8798
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
8799
 * elements, or VML.
8800
 *
8801
 * This file contains the state machine connectors.
8802
 *
8803
 * Thanks to Brainstorm Mobile Solutions for supporting the development of these.
8804
 *
8805
 * Copyright (c) 2010 - 2013 Simon Porritt ([email protected])
8806
 *
8807
 * http://jsplumb.org
8808
 * http://github.com/sporritt/jsplumb
8809
 * http://code.google.com/p/jsplumb
8810
 *
8811
 * Dual licensed under the MIT and GPL2 licenses.
8812
 */
8813
8814
;(function() {
8815
8816
	var Line = function(x1, y1, x2, y2) {
8817
8818
		this.m = (y2 - y1) / (x2 - x1);
8819
		this.b = -1 * ((this.m * x1) - y1);
8820
	
8821
		this.rectIntersect = function(x,y,w,h) {
8822
			var results = [], xInt, yInt;
8823
		
8824
			// 	try top face
8825
			// 	the equation of the top face is y = (0 * x) + b; y = b.
8826
			xInt = (y - this.b) / this.m;
8827
			// test that the X value is in the line's range.
8828
			if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
8829
		
8830
			// try right face
8831
			yInt = (this.m * (x + w)) + this.b;
8832
			if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
8833
		
8834
			// 	bottom face
8835
			xInt = ((y + h) - this.b) / this.m;
8836
			// test that the X value is in the line's range.
8837
			if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
8838
		
8839
			// try left face
8840
			yInt = (this.m * x) + this.b;
8841
			if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
8842
8843
			if (results.length == 2) {
8844
				var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2;
8845
				results.push([ midx,midy ]);
8846
				// now calculate the segment inside the rectangle where the midpoint lies.
8847
				var xseg = midx <= x + (w / 2) ? -1 : 1,
8848
					yseg = midy <= y + (h / 2) ? -1 : 1;
8849
				results.push([xseg, yseg]);
8850
				return results;
8851
			}
8852
		
8853
			return null;
8854
8855
		};
8856
	},
8857
	_segment = function(x1, y1, x2, y2) {
8858
		if (x1 <= x2 && y2 <= y1) return 1;
8859
		else if (x1 <= x2 && y1 <= y2) return 2;
8860
		else if (x2 <= x1 && y2 >= y1) return 3;
8861
		return 4;
8862
	},
8863
		
8864
		// the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the
8865
		// two faces are parallel or perpendicular.  if they are parallel then the control point lies on the midpoint of the axis in which they
8866
		// are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the
8867
		// center of that face.  if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and
8868
		// direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element
8869
		// lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left.
8870
		//
8871
		// sourcePos and targetPos are arrays of info about where on the source and target each anchor is located.  their contents are:
8872
		//
8873
		// 0 - absolute x
8874
		// 1 - absolute y
8875
		// 2 - proportional x in element (0 is left edge, 1 is right edge)
8876
		// 3 - proportional y in element (0 is top edge, 1 is bottom edge)
8877
		// 	
8878
	_findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) {
8879
        // TODO (maybe)
8880
        // - if anchor pos is 0.5, make the control point take into account the relative position of the elements.
8881
        if (distance <= proximityLimit) return [midx, midy];
8882
8883
        if (segment === 1) {
8884
            if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
8885
            else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
8886
            else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
8887
        }
8888
        else if (segment === 2) {
8889
            if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
8890
            else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
8891
            else return [ midx + (1 * dx) , midy + (-1 * dy) ];
8892
        }
8893
        else if (segment === 3) {
8894
            if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
8895
            else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
8896
            else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
8897
        }
8898
        else if (segment === 4) {
8899
            if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
8900
            else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
8901
            else return [ midx + (1 * dx) , midy + (-1 * dy) ];
8902
        }
8903
8904
	};	
8905
	
8906
	/**
8907
     * Class: Connectors.StateMachine
8908
     * Provides 'state machine' connectors.
8909
     */
8910
	/*
8911
	 * Function: Constructor
8912
	 * 
8913
	 * Parameters:
8914
	 * curviness -	measure of how "curvy" the connectors will be.  this is translated as the distance that the
8915
     *                Bezier curve's control point is from the midpoint of the straight line connecting the two
8916
     *              endpoints, and does not mean that the connector is this wide.  The Bezier curve never reaches
8917
     *              its control points; they act as gravitational masses. defaults to 10.
8918
	 * margin	-	distance from element to start and end connectors, in pixels.  defaults to 5.
8919
	 * proximityLimit  -   sets the distance beneath which the elements are consider too close together to bother
8920
	 *						with fancy curves. by default this is 80 pixels.
8921
	 * loopbackRadius	-	the radius of a loopback connector.  optional; defaults to 25.
8922
	 * showLoopback   -   If set to false this tells the connector that it is ok to paint connections whose source and target is the same element with a connector running through the element. The default value for this is true; the connector always makes a loopback connection loop around the element rather than passing through it.
8923
	*/
8924
	var StateMachine = function(params) {
8925
		params = params || {};
8926
		this.type = "StateMachine";
8927
8928
		var self = this,
8929
			_super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
8930
			curviness = params.curviness || 10,
8931
			margin = params.margin || 5,
8932
			proximityLimit = params.proximityLimit || 80,
8933
			clockwise = params.orientation && params.orientation === "clockwise",
8934
			loopbackRadius = params.loopbackRadius || 25,
8935
			showLoopback = params.showLoopback !== false;
8936
		
8937
		this._compute = function(paintInfo, params) {
8938
			var w = Math.abs(params.sourcePos[0] - params.targetPos[0]),
8939
				h = Math.abs(params.sourcePos[1] - params.targetPos[1]),
8940
				x = Math.min(params.sourcePos[0], params.targetPos[0]),
8941
				y = Math.min(params.sourcePos[1], params.targetPos[1]);				
8942
		
8943
			if (!showLoopback || (params.sourceEndpoint.elementId !== params.targetEndpoint.elementId)) {                            
8944
				var _sx = params.sourcePos[0] < params.targetPos[0] ? 0  : w,
8945
					_sy = params.sourcePos[1] < params.targetPos[1] ? 0:h,
8946
					_tx = params.sourcePos[0] < params.targetPos[0] ? w : 0,
8947
					_ty = params.sourcePos[1] < params.targetPos[1] ? h : 0;
8948
            
8949
				// now adjust for the margin
8950
				if (params.sourcePos[2] === 0) _sx -= margin;
8951
            	if (params.sourcePos[2] === 1) _sx += margin;
8952
            	if (params.sourcePos[3] === 0) _sy -= margin;
8953
            	if (params.sourcePos[3] === 1) _sy += margin;
8954
            	if (params.targetPos[2] === 0) _tx -= margin;
8955
            	if (params.targetPos[2] === 1) _tx += margin;
8956
            	if (params.targetPos[3] === 0) _ty -= margin;
8957
            	if (params.targetPos[3] === 1) _ty += margin;
8958
8959
            	//
8960
	            // these connectors are quadratic bezier curves, having a single control point. if both anchors 
8961
    	        // are located at 0.5 on their respective faces, the control point is set to the midpoint and you
8962
        	    // get a straight line.  this is also the case if the two anchors are within 'proximityLimit', since
8963
           	 	// it seems to make good aesthetic sense to do that. outside of that, the control point is positioned 
8964
           	 	// at 'curviness' pixels away along the normal to the straight line connecting the two anchors.
8965
	            // 
8966
   	        	// there may be two improvements to this.  firstly, we might actually support the notion of avoiding nodes
8967
            	// in the UI, or at least making a good effort at doing so.  if a connection would pass underneath some node,
8968
            	// for example, we might increase the distance the control point is away from the midpoint in a bid to
8969
            	// steer it around that node.  this will work within limits, but i think those limits would also be the likely
8970
            	// limits for, once again, aesthetic good sense in the layout of a chart using these connectors.
8971
            	//
8972
            	// the second possible change is actually two possible changes: firstly, it is possible we should gradually
8973
            	// decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some
8974
            	// point (which should be configurable).  secondly, we might slightly increase the 'curviness' for connectors
8975
            	// with respect to how far their anchor is from the center of its respective face. this could either look cool,
8976
            	// or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time.
8977
            	//
8978
8979
				var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2, 
8980
            	    m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2),
8981
            	    dy =  (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)),
8982
				    dx =  (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)),
8983
				    segment = _segment(_sx, _sy, _tx, _ty),
8984
				    distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2)),			
8985
	            	// calculate the control point.  this code will be where we'll put in a rudimentary element avoidance scheme; it
8986
	            	// will work by extending the control point to force the curve to be, um, curvier.
8987
					_controlPoint = _findControlPoint(_midx,
8988
                                                  _midy,
8989
                                                  segment,
8990
                                                  params.sourcePos,
8991
                                                  params.targetPos,
8992
                                                  curviness, curviness,
8993
                                                  distance,
8994
                                                  proximityLimit);
8995
8996
				_super.addSegment(this, "Bezier", {
8997
					x1:_tx, y1:_ty, x2:_sx, y2:_sy,
8998
					cp1x:_controlPoint[0], cp1y:_controlPoint[1],
8999
					cp2x:_controlPoint[0], cp2y:_controlPoint[1]
9000
				});				
9001
            }
9002
            else {
9003
            	// a loopback connector.  draw an arc from one anchor to the other.            	
9004
        		var x1 = params.sourcePos[0], x2 = params.sourcePos[0], y1 = params.sourcePos[1] - margin, y2 = params.sourcePos[1] - margin, 				
9005
					cx = x1, cy = y1 - loopbackRadius,				
9006
					// canvas sizing stuff, to ensure the whole painted area is visible.
9007
					_w = 2 * loopbackRadius, 
9008
					_h = 2 * loopbackRadius,
9009
					_x = cx - loopbackRadius, 
9010
					_y = cy - loopbackRadius;
9011
9012
				paintInfo.points[0] = _x;
9013
				paintInfo.points[1] = _y;
9014
				paintInfo.points[2] = _w;
9015
				paintInfo.points[3] = _h;
9016
				
9017
				// ADD AN ARC SEGMENT.
9018
				_super.addSegment(this, "Arc", {
9019
					loopback:true,
9020
					x1:(x1 - _x) + 4,
9021
					y1:y1 - _y,
9022
					startAngle:0,
9023
					endAngle: 2 * Math.PI,
9024
					r:loopbackRadius,
9025
					ac:!clockwise,
9026
					x2:(x1 - _x) - 4,
9027
					y2:y1 - _y,
9028
					cx:cx - _x,
9029
					cy:cy - _y
9030
				});
9031
            }                           
9032
        };                        
9033
	};
9034
	jsPlumb.registerConnectorType(StateMachine, "StateMachine");
9035
})();
9036
9037
/*
9038
    	// a possible rudimentary avoidance scheme, old now, perhaps not useful.
9039
        //      if (avoidSelector) {
9040
		//		    var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty);
9041
		//		    var sel = jsPlumb.getSelector(avoidSelector);
9042
		//		    for (var i = 0; i < sel.length; i++) {
9043
		//			    var id = jsPlumb.getId(sel[i]);
9044
		//			    if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) {
9045
		//				    o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id);
9046
//
9047
//						    if (o && s) {
9048
//							    var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]);
9049
//							    if (collision) {
9050
								    // set the control point to be a certain distance from the midpoint of the two points that
9051
								    // the line crosses on the rectangle.
9052
								    // TODO where will this 75 number come from?
9053
					//			    _controlX = collision[2][0] + (75 * collision[3][0]);
9054
				//	/			    _controlY = collision[2][1] + (75 * collision[3][1]);
9055
//							    }
9056
//						    }
9057
					//  }
9058
	//			    }
9059
              //}
9060
    */
9061
9062
;(function() {
9063
9064
	var Bezier = function(params) {
9065
        params = params || {};
9066
9067
    	var self = this,
9068
			_super =  jsPlumb.Connectors.AbstractConnector.apply(this, arguments),
9069
            stub = params.stub || 50,
9070
            majorAnchor = params.curviness || 150,
9071
            minorAnchor = 10;            
9072
9073
        this.type = "Bezier";	
9074
        this.getCurviness = function() { return majorAnchor; };	
9075
        
9076
        this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint) {
9077
        	// determine if the two anchors are perpendicular to each other in their orientation.  we swap the control 
9078
        	// points around if so (code could be tightened up)
9079
        	var soo = sourceEndpoint.anchor.getOrientation(sourceEndpoint), 
9080
        		too = targetEndpoint.anchor.getOrientation(targetEndpoint),
9081
        		perpendicular = soo[0] != too[0] || soo[1] == too[1],
9082
            	p = [];                
9083
            	
9084
            if (!perpendicular) {
9085
                if (soo[0] === 0) // X
9086
                    p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
9087
                else p.push(point[0] - (majorAnchor * soo[0]));
9088
                                 
9089
                if (soo[1] === 0) // Y
9090
                	p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
9091
                else p.push(point[1] + (majorAnchor * too[1]));
9092
            }
9093
             else {
9094
                if (too[0] === 0) // X
9095
                	p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + minorAnchor : point[0] - minorAnchor);
9096
                else p.push(point[0] + (majorAnchor * too[0]));
9097
                
9098
                if (too[1] === 0) // Y
9099
                	p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + minorAnchor : point[1] - minorAnchor);
9100
                else p.push(point[1] + (majorAnchor * soo[1]));
9101
             }
9102
9103
            return p;                
9104
        };        
9105
9106
        this._compute = function(paintInfo, p) {                                
9107
			var sp = p.sourcePos,
9108
				tp = p.targetPos,				
9109
                _w = Math.abs(sp[0] - tp[0]),
9110
                _h = Math.abs(sp[1] - tp[1]),            
9111
                _sx = sp[0] < tp[0] ? _w : 0,
9112
                _sy = sp[1] < tp[1] ? _h : 0,
9113
                _tx = sp[0] < tp[0] ? 0 : _w,
9114
                _ty = sp[1] < tp[1] ? 0 : _h,
9115
                _CP = self._findControlPoint([_sx, _sy], sp, tp, p.sourceEndpoint, p.targetEndpoint),
9116
                _CP2 = self._findControlPoint([_tx, _ty], tp, sp, p.targetEndpoint, p.sourceEndpoint);
9117
9118
			_super.addSegment(this, "Bezier", {
9119
				x1:_sx, y1:_sy, x2:_tx, y2:_ty,
9120
				cp1x:_CP[0], cp1y:_CP[1], cp2x:_CP2[0], cp2y:_CP2[1]
9121
			});                    
9122
        }; 
9123
	};
9124
9125
	jsPlumb.registerConnectorType(Bezier, "Bezier");
9126
9127
})();
9128
/*
9129
 * jsPlumb
9130
 * 
9131
 * Title:jsPlumb 1.5.5
9132
 * 
9133
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
9134
 * elements, or VML.  
9135
 * 
9136
 * This file contains the HTML5 canvas renderers.  Support for canvas was dropped in 1.4.2.
9137
 * This is being kept around because canvas might make a comeback as a single-page solution
9138
 * that also supports node rendering.
9139
 *
9140
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
9141
 * 
9142
 * http://jsplumb.org
9143
 * http://github.com/sporritt/jsplumb
9144
 * http://code.google.com/p/jsplumb
9145
 * 
9146
 * Dual licensed under the MIT and GPL2 licenses.
9147
 */
9148
9149
;(function() {
9150
9151
	
9152
// ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS *******************************************************************
9153
		
9154
	// TODO refactor to renderer common script.  put a ref to jsPlumb.sizeCanvas in there too.
9155
	var _connectionBeingDragged = null,
9156
	    _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
9157
	    _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
9158
	    _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
9159
	    _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); },
9160
	    _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); };
9161
	
9162
	/*
9163
	 * Class:CanvasMouseAdapter
9164
	 * Provides support for mouse events on canvases.  
9165
	 */
9166
	var CanvasMouseAdapter = window.CanvasMouseAdapter = function() {
9167
		var self = this;
9168
		this.overlayPlacements = [];
9169
		jsPlumb.jsPlumbUIComponent.apply(this, arguments);
9170
		jsPlumbUtil.EventGenerator.apply(this, arguments);
9171
		/**
9172
		 * returns whether or not the given event is ojver a painted area of the canvas. 
9173
		 */
9174
	    this._over = function(e) {		    			  		    	
9175
			var o = _getOffset(_getElementObject(self.canvas)),
9176
				pageXY = _pageXY(e),
9177
				x = pageXY[0] - o.left, y = pageXY[1] - o.top;
9178
			if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) {
9179
				// first check overlays
9180
				for ( var i = 0; i < self.overlayPlacements.length; i++) {
9181
					var p = self.overlayPlacements[i];
9182
					if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y))
9183
						return true;
9184
				}		    	
9185
				// then the canvas
9186
				var d = self.canvas.getContext("2d").getImageData(parseInt(x, 10), parseInt(y, 10), 1, 1);
9187
				return d.data[0] !== 0 || d.data[1] !== 0 || d.data[2] !== 0 || d.data[3] !== 0;		  
9188
			}
9189
			return false;
9190
	    };
9191
	    
9192
	    var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false,
9193
		    _nullSafeHasClass = function(el, clazz) {
9194
		    	return el !== null && _hasClass(el, clazz);
9195
		    };
9196
	    this.mousemove = function(e) {		    
9197
	    	var pageXY = _pageXY(e), clientXY = _clientXY(e),	   
9198
	    	ee = document.elementFromPoint(clientXY[0], clientXY[1]),
9199
	    	eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay");	    	
9200
			var _continue = _connectionBeingDragged === null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector"));
9201
			if (!_mouseover && _continue && self._over(e)) {
9202
				_mouseover = true;
9203
				self.fire("mouseenter", self, e);		
9204
				return true;
9205
			}
9206
			// TODO here there is a remote chance that the overlay the mouse moved onto
9207
			// is actually not an overlay for the current component. a more thorough check would
9208
			// be to ensure the overlay belonged to the current component.  
9209
			else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) {
9210
				_mouseover = false;
9211
				self.fire("mouseexit", self, e);				
9212
			}
9213
			self.fire("mousemove", self, e);
9214
	    };
9215
	    		    		    
9216
	    this.click = function(e) {	    		
9217
			if (_mouseover && self._over(e) && !_mouseWasDown) 
9218
	    		self.fire("click", self, e);		    	
9219
	    	_mouseWasDown = false;
9220
	    };
9221
	    
9222
	    this.dblclick = function(e) {
9223
	    	if (_mouseover && self._over(e) && !_mouseWasDown) 
9224
	    		self.fire("dblclick", self, e);		    	
9225
	    	_mouseWasDown = false;
9226
	    };
9227
	    
9228
	    this.mousedown = function(e) {
9229
	    	if(self._over(e) && !_mouseDown) {
9230
	    		_mouseDown = true;	    		
9231
	    		_posWhenMouseDown = _getOffset(_getElementObject(self.canvas));
9232
	    		self.fire("mousedown", self, e);
9233
	    	}
9234
	    };
9235
	    
9236
	    this.mouseup = function(e) {
9237
	    	_mouseDown = false;
9238
	    	self.fire("mouseup", self, e);
9239
	    };
9240
9241
        this.contextmenu = function(e) {
9242
          if (_mouseover && self._over(e) && !_mouseWasDown)
9243
            self.fire("contextmenu", self, e);
9244
          _mouseWasDown = false;
9245
        };
9246
	};
9247
	jsPlumbUtil.extend(CanvasMouseAdapter, [ jsPlumb.jsPlumbUIComponent, jsPlumbUtil.EventGenerator ]);		
9248
	
9249
	var _newCanvas = function(params) {
9250
		var canvas = document.createElement("canvas");
9251
		params._jsPlumb.instance.appendElement(canvas, params.parent);
9252
		canvas.style.position = "absolute";
9253
		if (params["class"]) canvas.className = params["class"];
9254
		// set an id. if no id on the element and if uuid was supplied it
9255
		// will be used, otherwise we'll create one.
9256
		params._jsPlumb.instance.getId(canvas, params.uuid);
9257
		if (params.tooltip) canvas.setAttribute("title", params.tooltip);
9258
9259
		return canvas;
9260
	};	
9261
9262
	var CanvasComponent = window.CanvasComponent = function(params) {
9263
		CanvasMouseAdapter.apply(this, arguments);
9264
9265
		var displayElements = [ ];
9266
		this.getDisplayElements = function() { return displayElements; };
9267
		this.appendDisplayElement = function(el) { displayElements.push(el); };
9268
	};
9269
	jsPlumbUtil.extend(CanvasComponent, CanvasMouseAdapter, {
9270
		setVisible:function(state) { 			
9271
			this.canvas.style.display = state ? "block" : "none";
9272
		}
9273
	});
9274
	
9275
	var segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ];
9276
	var maybeMakeGradient = function(ctx, style, gradientFunction) {
9277
		if (style.gradient) {
9278
			var g = gradientFunction();
9279
			for ( var i = 0; i < style.gradient.stops.length; i++)
9280
				g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
9281
			ctx.strokeStyle = g;
9282
		}
9283
	};
9284
	var segmentRenderer = function(segment, ctx, style, dx, dy) {	
9285
		({
9286
			"Straight":function(segment, ctx, style, dx, dy) {
9287
				var d = segment.params;
9288
				ctx.save();
9289
				maybeMakeGradient(ctx, style, function() { return ctx.createLinearGradient(d.x1, d.y1, d.x2, d.y2); });
9290
				ctx.beginPath();
9291
				ctx.translate(dx, dy);				
9292
				if (style.dashstyle && style.dashstyle.split(" ").length === 2) {			
9293
					// only a very simple dashed style is supported - having two values, which define the stroke length 
9294
					// (as a multiple of the stroke width) and then the space length (also as a multiple of stroke width). 
9295
					var ds = style.dashstyle.split(" ");
9296
					if (ds.length !== 2) ds = [2, 2];
9297
					var dss = [ ds[0] * style.lineWidth, ds[1] * style.lineWidth ],
9298
						m = (d.x2- d.x1) / (d.y2 - d.y1),
9299
						s = jsPlumbUtil.segment([d.x1, d.y1], [ d.x2, d.y2 ]),
9300
						sm = segmentMultipliers[s],
9301
						theta = Math.atan(m),
9302
						l = Math.sqrt(Math.pow(d.x2 - d.x1, 2) + Math.pow(d.y2 - d.y1, 2)),
9303
						repeats = Math.floor(l / (dss[0] + dss[1])),
9304
						curPos = [d.x1, d.y1];
9305
9306
					
9307
					// TODO: the question here is why could we not support this in all connector types? it's really
9308
					// just a case of going along and asking jsPlumb for the next point on the path a few times, until it
9309
					// reaches the end. every type of connector supports that method, after all.  but right now its only the
9310
					// bezier connector that gives you back the new location on the path along with the x,y coordinates, which
9311
					// we would need. we'd start out at loc=0 and ask for the point along the path that is dss[0] pixels away.
9312
					// we then ask for the point that is (dss[0] + dss[1]) pixels away; and from that one we need not just the
9313
					// x,y but the location, cos we're gonna plug that location back in in order to find where that dash ends.
9314
					//
9315
					// it also strikes me that it should be trivial to support arbitrary dash styles (having more or less than two
9316
					// entries). you'd just iterate that array using a step size of 2, and generify the (rss[0] + rss[1])
9317
					// computation to be sum(rss[0]..rss[n]).					
9318
9319
					for (var i = 0; i < repeats; i++) {
9320
						ctx.moveTo(curPos[0], curPos[1]);
9321
9322
						var nextEndX = curPos[0] + (Math.abs(Math.sin(theta) * dss[0]) * sm[0]),
9323
							nextEndY = curPos[1] + (Math.abs(Math.cos(theta) * dss[0]) * sm[1]),
9324
							nextStartX = curPos[0] + (Math.abs(Math.sin(theta) * (dss[0] + dss[1]))  * sm[0]),
9325
							nextStartY = curPos[1] + (Math.abs(Math.cos(theta) * (dss[0] + dss[1])) * sm[1]);
9326
9327
						ctx.lineTo(nextEndX, nextEndY);
9328
						curPos = [nextStartX, nextStartY];					
9329
					}
9330
9331
					// now draw the last bit
9332
					ctx.moveTo(curPos[0], curPos[1]);
9333
					ctx.lineTo(d.x2, d.y2);							
9334
9335
				}	        
9336
		        else {
9337
					ctx.moveTo(d.x1, d.y1);
9338
					ctx.lineTo(d.x2, d.y2);
9339
		        }				
9340
9341
				ctx.stroke();
9342
9343
				ctx.restore();
9344
			},
9345
			"Bezier":function(segment, ctx, style, dx, dy) {				
9346
				var d = segment.params;
9347
				ctx.save();
9348
				maybeMakeGradient(ctx, style, function() { return ctx.createLinearGradient(d.x2 + dx, d.y2 + dy, d.x1 + dx, d.y1 + dy); });
9349
				ctx.beginPath();
9350
				ctx.translate(dx, dy);
9351
				ctx.moveTo(d.x1, d.y1);
9352
				ctx.bezierCurveTo(d.cp1x, d.cp1y, d.cp2x, d.cp2y, d.x2, d.y2);
9353
				ctx.stroke();
9354
				ctx.restore();
9355
			},
9356
			"Arc":function(segment, ctx, style, dx, dy) {
9357
				var d = segment.params;
9358
				ctx.save();
9359
				ctx.beginPath();
9360
				ctx.translate(dx, dy);				
9361
				ctx.arc(d.cx, d.cy, d.r, segment.startAngle, segment.endAngle, d.ac);
9362
				ctx.stroke();
9363
				ctx.restore();
9364
			}
9365
		})[segment.type](segment, ctx, style, dx, dy);	
9366
	};
9367
	
9368
	/**
9369
	 * Class:CanvasConnector
9370
	 * Superclass for Canvas Connector renderers.
9371
	 */
9372
	var CanvasConnector = jsPlumb.ConnectorRenderers.canvas = function(params) {
9373
		CanvasComponent.apply(this, arguments);
9374
		
9375
		var _paintOneStyle = function(aStyle, dx, dy) {
9376
			this.ctx.save();
9377
			jsPlumb.extend(this.ctx, aStyle);
9378
9379
			var segments = this.getSegments();				
9380
			for (var i = 0; i < segments.length; i++) {
9381
				segmentRenderer(segments[i], this.ctx, aStyle, dx, dy);
9382
			}
9383
			this.ctx.restore();
9384
		}.bind(this);
9385
9386
		var clazz = this._jsPlumb.instance.connectorClass + " " + (params.cssClass || "");
9387
		this.canvas = _newCanvas({ 
9388
			"class":clazz, 
9389
			_jsPlumb:this._jsPlumb,
9390
			parent:params.parent
9391
		});	
9392
		this.ctx = this.canvas.getContext("2d");
9393
		
9394
		this.appendDisplayElement(this.canvas);
9395
		
9396
		this.paint = function(style, anchor, extents) {						
9397
			if (style != null) {							
9398
9399
				var xy = [ this.x, this.y ], wh = [ this.w, this.h ], p,
9400
					dx = 0, dy = 0;
9401
9402
				if (extents != null) {
9403
					if (extents.xmin < 0) {
9404
						xy[0] += extents.xmin;
9405
						dx = -extents.xmin;
9406
					}
9407
					if (extents.ymin < 0) {
9408
						xy[1] += extents.ymin;
9409
						dy = -extents.ymin;
9410
					}
9411
					wh[0] = extents.xmax + ((extents.xmin < 0) ? -extents.xmin : 0);
9412
					wh[1] = extents.ymax + ((extents.ymin < 0) ? -extents.ymin : 0);
9413
				}
9414
9415
				this.translateX = dx;
9416
				this.translateY = dy;
9417
				
9418
				jsPlumbUtil.sizeElement(this.canvas, xy[0], xy[1], wh[0], wh[1]);				
9419
				
9420
				if (style.outlineColor != null) {
9421
					var outlineWidth = style.outlineWidth || 1,
9422
					outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
9423
					outlineStyle = {
9424
						strokeStyle:style.outlineColor,
9425
						lineWidth:outlineStrokeWidth
9426
					};
9427
					_paintOneStyle(outlineStyle, dx, dy);
9428
				}
9429
				_paintOneStyle(style, dx, dy);
9430
			}
9431
		};				
9432
	};		
9433
	jsPlumbUtil.extend(CanvasConnector, CanvasComponent);
9434
		
9435
	
9436
	/**
9437
	 * Class:CanvasEndpoint
9438
	 * Superclass for Canvas Endpoint renderers.
9439
	 */
9440
	var CanvasEndpoint = function(params) {
9441
		CanvasComponent.apply(this, arguments);		
9442
		var clazz = this._jsPlumb.instance.endpointClass + " " + (params.cssClass || ""),
9443
			canvasParams = { 
9444
			"class":clazz, 
9445
			_jsPlumb:this._jsPlumb,
9446
			parent:params.parent,
9447
			tooltip:self.tooltip
9448
		};
9449
		this.canvas = _newCanvas(canvasParams);	
9450
		this.ctx = this.canvas.getContext("2d");
9451
9452
		this.appendDisplayElement(this.canvas);
9453
		
9454
		this.paint = function(style, anchor, extents) {
9455
			jsPlumbUtil.sizeElement(this.canvas, this.x, this.y, this.w, this.h);			
9456
			if (style.outlineColor != null) {
9457
				var outlineWidth = style.outlineWidth || 1,
9458
				outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
9459
				var outlineStyle = {
9460
					strokeStyle:style.outlineColor,
9461
					lineWidth:outlineStrokeWidth
9462
				};
9463
			}
9464
			
9465
			this._paint.apply(this, arguments);
9466
		};
9467
	};
9468
	jsPlumbUtil.extend(CanvasEndpoint, CanvasComponent);
9469
	
9470
	jsPlumb.Endpoints.canvas.Dot = function(params) {		
9471
		jsPlumb.Endpoints.Dot.apply(this, arguments);
9472
		CanvasEndpoint.apply(this, arguments);
9473
		var self = this,		
9474
		parseValue = function(value) {
9475
			try { return parseInt(value, 10); }
9476
			catch(e) {
9477
				if (value.substring(value.length - 1) == '%')
9478
					return parseInt(value.substring(0, value - 1), 10);
9479
			}
9480
		},					    	
9481
		calculateAdjustments = function(gradient) {
9482
			var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius;
9483
			if (gradient.offset) offsetAdjustment = parseValue(gradient.offset);
9484
        	if (gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius);
9485
        	return [offsetAdjustment, innerRadius];
9486
		};
9487
		this._paint = function(style) {
9488
			if (style != null) {			
9489
				var ctx = self.canvas.getContext('2d'), 
9490
					orientation = params.endpoint.anchor.getOrientation(params.endpoint);
9491
9492
				jsPlumb.extend(ctx, style);							
9493
	            if (style.gradient) {            	
9494
	            	var adjustments = calculateAdjustments(style.gradient), 
9495
	            	yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0],
9496
	            	xAdjust = orientation[0] == 1 ? adjustments[0] * -1:  adjustments[0],
9497
	            	g = ctx.createRadialGradient(self.radius, self.radius, self.radius, self.radius + xAdjust, self.radius + yAdjust, adjustments[1]);
9498
		            for (var i = 0; i < style.gradient.stops.length; i++)
9499
		            	g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
9500
		            ctx.fillStyle = g;
9501
	            }				
9502
				ctx.beginPath();    
9503
				//ctx.translate(dx, dy);						
9504
				ctx.arc(self.radius, self.radius, self.radius, 0, Math.PI*2, true);
9505
				ctx.closePath();				
9506
				if (style.fillStyle || style.gradient) ctx.fill();
9507
				if (style.strokeStyle) ctx.stroke();
9508
			}
9509
    	};
9510
	};	
9511
	jsPlumbUtil.extend(jsPlumb.Endpoints.canvas.Dot, [ jsPlumb.Endpoints.Dot, CanvasEndpoint ]);
9512
		
9513
	jsPlumb.Endpoints.canvas.Rectangle = function(params) {
9514
		
9515
		var self = this;
9516
		jsPlumb.Endpoints.Rectangle.apply(this, arguments);
9517
		CanvasEndpoint.apply(this, arguments);				
9518
		
9519
    	this._paint = function(style) {
9520
				
9521
			var ctx = self.canvas.getContext("2d"), 
9522
				orientation = params.endpoint.anchor.getOrientation(params.endpoint);
9523
9524
			jsPlumb.extend(ctx, style);
9525
			
9526
			/* canvas gradient */
9527
		    if (style.gradient) {
9528
		    	// first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
9529
		    	var y1 = orientation[1] == 1 ? self.h : orientation[1] === 0 ? self.h / 2 : 0;
9530
				var y2 = orientation[1] == -1 ? self.h : orientation[1] === 0 ? self.h / 2 : 0;
9531
				var x1 = orientation[0] == 1 ? self.w : orientation[0] === 0 ? self.w / 2 : 0;
9532
				var x2 = orientation[0] == -1 ? self.w : orientation[0] === 0 ? self.w / 2 : 0;
9533
			    var g = ctx.createLinearGradient(x1,y1,x2,y2);
9534
			    for (var i = 0; i < style.gradient.stops.length; i++)
9535
	            	g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
9536
	            ctx.fillStyle = g;
9537
		    }
9538
			
9539
			ctx.beginPath();
9540
			ctx.rect(0, 0, self.w, self.h);
9541
			ctx.closePath();				
9542
			if (style.fillStyle || style.gradient) ctx.fill();
9543
			if (style.strokeStyle) ctx.stroke();
9544
    	};
9545
	};		
9546
	jsPlumbUtil.extend(jsPlumb.Endpoints.canvas.Rectangle, [ jsPlumb.Endpoints.Rectangle, CanvasEndpoint ]);
9547
	
9548
	jsPlumb.Endpoints.canvas.Triangle = function(params) {
9549
	        			
9550
		var self = this;
9551
		jsPlumb.Endpoints.Triangle.apply(this, arguments);
9552
		CanvasEndpoint.apply(this, arguments);			
9553
		
9554
    	this._paint = function(style) {    					
9555
			var ctx = self.canvas.getContext('2d'),
9556
				offsetX = 0, offsetY = 0, angle = 0,
9557
				orientation = params.endpoint.anchor.getOrientation(params.endpoint);
9558
			
9559
			if( orientation[0] == 1 ) {
9560
				offsetX = self.width;
9561
				offsetY = self.height;
9562
				angle = 180;
9563
			}
9564
			if( orientation[1] == -1 ) {
9565
				offsetX = self.width;
9566
				angle = 90;
9567
			}
9568
			if( orientation[1] == 1 ) {
9569
				offsetY = self.height;
9570
				angle = -90;
9571
			}
9572
			
9573
			ctx.fillStyle = style.fillStyle;
9574
			
9575
			ctx.translate(offsetX, offsetY);
9576
			ctx.rotate(angle * Math.PI/180);
9577
9578
			ctx.beginPath();
9579
			ctx.moveTo(0, 0);
9580
			ctx.lineTo(self.width/2, self.height/2);
9581
			ctx.lineTo(0, self.height);
9582
			ctx.closePath();
9583
			if (style.fillStyle || style.gradient) ctx.fill();
9584
			if (style.strokeStyle) ctx.stroke();				
9585
    	};
9586
	};	
9587
	jsPlumbUtil.extend(jsPlumb.Endpoints.canvas.Triangle, [ jsPlumb.Endpoints.Triangle, CanvasEndpoint ]);
9588
	
9589
	/*
9590
	 * Canvas Image Endpoint: uses the default version, which creates an <img> tag.
9591
	 */
9592
	jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image;
9593
	
9594
	/*
9595
	 * Blank endpoint in all renderers is just the default Blank endpoint.
9596
	 */
9597
	jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank;
9598
		
9599
// ********************************* END OF CANVAS RENDERERS *******************************************************************    
9600
    
9601
    jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label;
9602
	jsPlumb.Overlays.canvas.Custom = jsPlumb.Overlays.Custom;
9603
    
9604
    /**
9605
     * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this. 
9606
     */
9607
    var CanvasOverlay = function() { 
9608
    	jsPlumb.jsPlumbUIComponent.apply(this, arguments);
9609
    };
9610
    jsPlumbUtil.extend(CanvasOverlay, jsPlumb.jsPlumbUIComponent, {
9611
    	setVisible : function(state) {
9612
    	    this.visible = state;
9613
    	    this.component.repaint();
9614
    	}
9615
    });
9616
    
9617
    var AbstractCanvasArrowOverlay = function(superclass, originalArgs) {
9618
    	superclass.apply(this, originalArgs);
9619
    	CanvasOverlay.apply(this, originalArgs);
9620
    	this.paint = function(params, containerExtents) {
9621
    		var ctx = params.component.ctx, d = params.d;
9622
    		
9623
    		if (d) {
9624
    			ctx.save();
9625
				ctx.lineWidth = params.lineWidth;
9626
				ctx.beginPath();
9627
				ctx.translate(params.component.translateX, params.component.translateY);
9628
				ctx.moveTo(d.hxy.x, d.hxy.y);
9629
				ctx.lineTo(d.tail[0].x, d.tail[0].y);
9630
				ctx.lineTo(d.cxy.x, d.cxy.y);
9631
				ctx.lineTo(d.tail[1].x, d.tail[1].y);
9632
				ctx.lineTo(d.hxy.x, d.hxy.y);
9633
				ctx.closePath();						
9634
							
9635
				if (params.strokeStyle) {
9636
					ctx.strokeStyle = params.strokeStyle;
9637
					ctx.stroke();
9638
				}
9639
				if (params.fillStyle) {
9640
					ctx.fillStyle = params.fillStyle;			
9641
					ctx.fill();
9642
				}
9643
				ctx.restore();
9644
			}
9645
    	};
9646
    }; 
9647
    
9648
    jsPlumb.Overlays.canvas.Arrow = function() {
9649
    	AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);    	
9650
    };
9651
    jsPlumbUtil.extend(jsPlumb.Overlays.canvas.Arrow, [ jsPlumb.Overlays.Arrow, CanvasOverlay ] );
9652
    
9653
    jsPlumb.Overlays.canvas.PlainArrow = function() {
9654
    	AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);    	
9655
    };
9656
    jsPlumbUtil.extend(jsPlumb.Overlays.canvas.PlainArrow, [ jsPlumb.Overlays.PlainArrow, CanvasOverlay ] );
9657
    
9658
    jsPlumb.Overlays.canvas.Diamond = function() {
9659
    	AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);    	
9660
    };		
9661
    jsPlumbUtil.extend(jsPlumb.Overlays.canvas.Diamond, [ jsPlumb.Overlays.Diamond, CanvasOverlay ] );
9662
})();
9663
/*
9664
 * jsPlumb
9665
 * 
9666
 * Title:jsPlumb 1.5.5
9667
 * 
9668
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
9669
 * elements, or VML.  
9670
 * 
9671
 * This file contains the SVG renderers.
9672
 *
9673
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
9674
 * 
9675
 * http://jsplumb.org
9676
 * http://github.com/sporritt/jsplumb
9677
 * http://code.google.com/p/jsplumb
9678
 * 
9679
 * Dual licensed under the MIT and GPL2 licenses.
9680
 */
9681
9682
/**
9683
 * SVG support for jsPlumb.
9684
 * 
9685
 * things to investigate:
9686
 * 
9687
 * gradients:  https://developer.mozilla.org/en/svg_in_html_introduction
9688
 * css:http://tutorials.jenkov.com/svg/svg-and-css.html
9689
 * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath
9690
 * pointer events: https://developer.mozilla.org/en/css/pointer-events
9691
 *
9692
 * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events
9693
 *
9694
 */
9695
;(function() {
9696
	
9697
// ************************** SVG utility methods ********************************************	
9698
	
9699
	var svgAttributeMap = {
9700
		"joinstyle":"stroke-linejoin",
9701
		"stroke-linejoin":"stroke-linejoin",		
9702
		"stroke-dashoffset":"stroke-dashoffset",
9703
		"stroke-linecap":"stroke-linecap"
9704
	},
9705
	STROKE_DASHARRAY = "stroke-dasharray",
9706
	DASHSTYLE = "dashstyle",
9707
	LINEAR_GRADIENT = "linearGradient",
9708
	RADIAL_GRADIENT = "radialGradient",
9709
	FILL = "fill",
9710
	STOP = "stop",
9711
	STROKE = "stroke",
9712
	STROKE_WIDTH = "stroke-width",
9713
	STYLE = "style",
9714
	NONE = "none",
9715
	JSPLUMB_GRADIENT = "jsplumb_gradient_",
9716
	LINE_WIDTH = "lineWidth",
9717
	ns = {
9718
		svg:"http://www.w3.org/2000/svg",
9719
		xhtml:"http://www.w3.org/1999/xhtml"
9720
	},
9721
	_attr = function(node, attributes) {
9722
		for (var i in attributes)
9723
			node.setAttribute(i, "" + attributes[i]);
9724
	},	
9725
	_node = function(name, attributes) {
9726
		var n = document.createElementNS(ns.svg, name);
9727
		attributes = attributes || {};
9728
		attributes.version = "1.1";
9729
		attributes.xmlns = ns.xhtml;
9730
		_attr(n, attributes);
9731
		return n;
9732
	},
9733
	_pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; },	
9734
	_clearGradient = function(parent) {
9735
		for (var i = 0; i < parent.childNodes.length; i++) {
9736
			if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT)
9737
				parent.removeChild(parent.childNodes[i]);
9738
		}
9739
	},		
9740
	_updateGradient = function(parent, node, style, dimensions, uiComponent) {
9741
		var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.instance.idstamp();
9742
		// first clear out any existing gradient
9743
		_clearGradient(parent);
9744
		// this checks for an 'offset' property in the gradient, and in the absence of it, assumes
9745
		// we want a linear gradient. if it's there, we create a radial gradient.
9746
		// it is possible that a more explicit means of defining the gradient type would be
9747
		// better. relying on 'offset' means that we can never have a radial gradient that uses
9748
		// some default offset, for instance.
9749
		// issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would
9750
		// not show gradients when the line was perfectly horizontal or vertical.
9751
		var g;
9752
		if (!style.gradient.offset) {
9753
			g = _node(LINEAR_GRADIENT, {id:id, gradientUnits:"userSpaceOnUse"});
9754
		}
9755
		else {
9756
			g = _node(RADIAL_GRADIENT, {
9757
				id:id
9758
			});			
9759
		}
9760
		
9761
		parent.appendChild(g);
9762
		
9763
		// the svg radial gradient seems to treat stops in the reverse 
9764
		// order to how canvas does it.  so we want to keep all the maths the same, but
9765
		// iterate the actual style declarations in reverse order, if the x indexes are not in order.
9766
		for (var i = 0; i < style.gradient.stops.length; i++) {
9767
			var styleToUse = uiComponent.segment == 1 ||  uiComponent.segment == 2 ? i: style.gradient.stops.length - 1 - i,			
9768
				stopColor = jsPlumbUtil.convertStyle(style.gradient.stops[styleToUse][1], true),
9769
				s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor});
9770
9771
			g.appendChild(s);
9772
		}
9773
		var applyGradientTo = style.strokeStyle ? STROKE : FILL;
9774
        //document.location.toString()
9775
		//node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")");
9776
        node.setAttribute(STYLE, applyGradientTo + ":url(" + document.location.toString() + "#" + id + ")");
9777
	},
9778
	_applyStyles = function(parent, node, style, dimensions, uiComponent) {
9779
		
9780
		if (style.gradient) {
9781
			_updateGradient(parent, node, style, dimensions, uiComponent);			
9782
		}
9783
		else {
9784
			// make sure we clear any existing gradient
9785
			_clearGradient(parent);
9786
			node.setAttribute(STYLE, "");
9787
		}
9788
		
9789
		node.setAttribute(FILL, style.fillStyle ? jsPlumbUtil.convertStyle(style.fillStyle, true) : NONE);
9790
		node.setAttribute(STROKE, style.strokeStyle ? jsPlumbUtil.convertStyle(style.strokeStyle, true) : NONE);		
9791
		if (style.lineWidth) {
9792
			node.setAttribute(STROKE_WIDTH, style.lineWidth);
9793
		}
9794
	
9795
		// in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
9796
		// the syntax in VML but is actually kind of nasty: values are given in the pixel
9797
		// coordinate space, whereas in VML they are multiples of the width of the stroked
9798
		// line, which makes a lot more sense.  for that reason, jsPlumb is supporting both
9799
		// the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
9800
		// VML, which will be the preferred method.  the code below this converts a dashstyle
9801
		// attribute given in terms of stroke width into a pixel representation, by using the
9802
		// stroke's lineWidth. 
9803
		if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
9804
			var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",",
9805
			parts = style[DASHSTYLE].split(sep),
9806
			styleToUse = "";
9807
			parts.forEach(function(p) {
9808
				styleToUse += (Math.floor(p * style.lineWidth) + sep);
9809
			});
9810
			node.setAttribute(STROKE_DASHARRAY, styleToUse);
9811
		}		
9812
		else if(style[STROKE_DASHARRAY]) {
9813
			node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
9814
		}
9815
		
9816
		// extra attributes such as join type, dash offset.
9817
		for (var i in svgAttributeMap) {
9818
			if (style[i]) {
9819
				node.setAttribute(svgAttributeMap[i], style[i]);
9820
			}
9821
		}
9822
	},
9823
	_decodeFont = function(f) {
9824
		var r = /([0-9].)(p[xt])\s(.*)/, 
9825
			bits = f.match(r);
9826
9827
		return {size:bits[1] + bits[2], font:bits[3]};		
9828
	},
9829
	_classManip = function(el, add, clazz) {
9830
		var classesToAddOrRemove = clazz.split(" "),
9831
			className = el.className,
9832
			curClasses = className.baseVal.split(" ");
9833
			
9834
		for (var i = 0; i < classesToAddOrRemove.length; i++) {
9835
			if (add) {
9836
				if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
9837
					curClasses.push(classesToAddOrRemove[i]);
9838
			}
9839
			else {
9840
				var idx = curClasses.indexOf(classesToAddOrRemove[i]);
9841
				if (idx != -1)
9842
					curClasses.splice(idx, 1);
9843
			}
9844
		}
9845
		
9846
		el.className.baseVal = curClasses.join(" ");
9847
	},
9848
	_addClass = function(el, clazz) { _classManip(el, true, clazz); },
9849
	_removeClass = function(el, clazz) { _classManip(el, false, clazz); },
9850
	_appendAtIndex = function(svg, path, idx) {
9851
		if (svg.childNodes.length > idx) {
9852
			svg.insertBefore(path, svg.childNodes[idx]);
9853
		}
9854
		else svg.appendChild(path);
9855
	};
9856
	
9857
	/**
9858
		utility methods for other objects to use.
9859
	*/
9860
	jsPlumbUtil.svg = {
9861
		addClass:_addClass,
9862
		removeClass:_removeClass,
9863
		node:_node,
9864
		attr:_attr,
9865
		pos:_pos
9866
	};
9867
	
9868
 // ************************** / SVG utility methods ********************************************	
9869
	
9870
	/*
9871
	 * Base class for SVG components.
9872
	 */	
9873
	var SvgComponent = function(params) {
9874
		var pointerEventsSpec = params.pointerEventsSpec || "all", renderer = {};
9875
			
9876
		jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs);
9877
		this.canvas = null;this.path = null;this.svg = null; 
9878
	
9879
		var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""),		
9880
			svgParams = {
9881
				"style":"",
9882
				"width":0,
9883
				"height":0,
9884
				"pointer-events":pointerEventsSpec,
9885
				"position":"absolute"
9886
			};				
9887
		this.svg = _node("svg", svgParams);
9888
		if (params.useDivWrapper) {
9889
			this.canvas = document.createElement("div");
9890
			this.canvas.style.position = "absolute";
9891
			jsPlumbUtil.sizeElement(this.canvas,0,0,1,1);
9892
			this.canvas.className = clazz;
9893
		}
9894
		else {
9895
			_attr(this.svg, { "class":clazz });
9896
			this.canvas = this.svg;
9897
		}
9898
			
9899
		params._jsPlumb.appendElement(this.canvas, params.originalArgs[0].parent);
9900
		if (params.useDivWrapper) this.canvas.appendChild(this.svg);
9901
		
9902
		// TODO this displayElement stuff is common between all components, across all
9903
		// renderers.  would be best moved to jsPlumbUIComponent.
9904
		var displayElements = [ this.canvas ];
9905
		this.getDisplayElements = function() { 
9906
			return displayElements; 
9907
		};
9908
		
9909
		this.appendDisplayElement = function(el) {
9910
			displayElements.push(el);
9911
		};	
9912
		
9913
		this.paint = function(style, anchor, extents) {	   			
9914
			if (style != null) {
9915
				
9916
				var xy = [ this.x, this.y ], wh = [ this.w, this.h ], p;
9917
				if (extents != null) {
9918
					if (extents.xmin < 0) xy[0] += extents.xmin;
9919
					if (extents.ymin < 0) xy[1] += extents.ymin;
9920
					wh[0] = extents.xmax + ((extents.xmin < 0) ? -extents.xmin : 0);
9921
					wh[1] = extents.ymax + ((extents.ymin < 0) ? -extents.ymin : 0);
9922
				}
9923
9924
				if (params.useDivWrapper) {					
9925
					jsPlumbUtil.sizeElement(this.canvas, xy[0], xy[1], wh[0], wh[1]);
9926
					xy[0] = 0; xy[1] = 0;
9927
					p = _pos([ 0, 0 ]);
9928
				}
9929
				else
9930
					p = _pos([ xy[0], xy[1] ]);
9931
                
9932
                renderer.paint.apply(this, arguments);		    			    	
9933
                
9934
		    	_attr(this.svg, {
9935
	    			"style":p,
9936
	    			"width": wh[0],
9937
	    			"height": wh[1]
9938
	    		});		    		    		    	
9939
			}
9940
	    };
9941
		
9942
		return {
9943
			renderer:renderer
9944
		};
9945
	};
9946
	jsPlumbUtil.extend(SvgComponent, jsPlumb.jsPlumbUIComponent, {
9947
		cleanup:function() {
9948
			jsPlumbUtil.removeElement(this.canvas);            
9949
			this.svg = null;
9950
			this.canvas = null;
9951
			this.path = null;			
9952
		},
9953
		setVisible:function(v) {
9954
			if (this.canvas) {
9955
				this.canvas.style.display = v ? "block" : "none";
9956
			}
9957
			if (this.bgCanvas) {
9958
				this.bgCanvas.style.display = v ? "block" : "none";
9959
			}
9960
		}
9961
	});
9962
	
9963
	/*
9964
	 * Base class for SVG connectors.
9965
	 */ 
9966
	var SvgConnector = jsPlumb.ConnectorRenderers.svg = function(params) {
9967
		var self = this,
9968
			_super = SvgComponent.apply(this, [ { 
9969
				cssClass:params._jsPlumb.connectorClass, 
9970
				originalArgs:arguments, 
9971
				pointerEventsSpec:"none", 
9972
				_jsPlumb:params._jsPlumb
9973
			} ]);	
9974
9975
		/*this.pointOnPath = function(location, absolute) {
9976
			if (!self.path) return [0,0];
9977
			var p = absolute ? location : location * self.path.getTotalLength();
9978
			return self.path.getPointAtLength(p);
9979
		};*/			
9980
9981
		_super.renderer.paint = function(style, anchor, extents) {
9982
			
9983
			var segments = self.getSegments(), p = "", offset = [0,0];			
9984
			if (extents.xmin < 0) offset[0] = -extents.xmin;
9985
			if (extents.ymin < 0) offset[1] = -extents.ymin;			
9986
			
9987
			// create path from segments.	
9988
			for (var i = 0; i < segments.length; i++) {
9989
				p += jsPlumb.Segments.svg.SegmentRenderer.getPath(segments[i]);
9990
				p += " ";
9991
			}			
9992
			
9993
			var a = { 
9994
					d:p,
9995
					transform:"translate(" + offset[0] + "," + offset[1] + ")",
9996
					"pointer-events":params["pointer-events"] || "visibleStroke"
9997
				}, 
9998
                outlineStyle = null,
9999
                d = [self.x,self.y,self.w,self.h];						
10000
			
10001
			// outline style.  actually means drawing an svg object underneath the main one.
10002
			if (style.outlineColor) {
10003
				var outlineWidth = style.outlineWidth || 1,
10004
					outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
10005
				outlineStyle = jsPlumb.CurrentLibrary.extend({}, style);
10006
				outlineStyle.strokeStyle = jsPlumbUtil.convertStyle(style.outlineColor);
10007
				outlineStyle.lineWidth = outlineStrokeWidth;
10008
				
10009
				if (self.bgPath == null) {
10010
					self.bgPath = _node("path", a);
10011
			    	_appendAtIndex(self.svg, self.bgPath, 0);
10012
		    		self.attachListeners(self.bgPath, self);
10013
				}
10014
				else {
10015
					_attr(self.bgPath, a);
10016
				}
10017
				
10018
				_applyStyles(self.svg, self.bgPath, outlineStyle, d, self);
10019
			}			
10020
			
10021
	    	if (self.path == null) {
10022
		    	self.path = _node("path", a);
10023
		    	_appendAtIndex(self.svg, self.path, style.outlineColor ? 1 : 0);
10024
	    		self.attachListeners(self.path, self);	    		    		
10025
	    	}
10026
	    	else {
10027
	    		_attr(self.path, a);
10028
	    	}
10029
	    		    	
10030
	    	_applyStyles(self.svg, self.path, style, d, self);
10031
		};
10032
		
10033
		this.reattachListeners = function() {
10034
			if (this.bgPath) this.reattachListenersForElement(this.bgPath, this);
10035
			if (this.path) this.reattachListenersForElement(this.path, this);
10036
		};
10037
	};
10038
	jsPlumbUtil.extend(jsPlumb.ConnectorRenderers.svg, SvgComponent);
10039
10040
// ******************************* svg segment renderer *****************************************************	
10041
		
10042
	jsPlumb.Segments.svg = {
10043
		SegmentRenderer : {		
10044
			getPath : function(segment) {
10045
				return ({
10046
					"Straight":function() {
10047
						var d = segment.getCoordinates();
10048
						return "M " + d.x1 + " " + d.y1 + " L " + d.x2 + " " + d.y2;	
10049
					},
10050
					"Bezier":function() {
10051
						var d = segment.params;
10052
						return "M " + d.x1 + " " + d.y1 + 
10053
							" C " + d.cp1x + " " + d.cp1y + " " + d.cp2x + " " + d.cp2y + " " + d.x2 + " " + d.y2;			
10054
					},
10055
					"Arc":function() {
10056
						var d = segment.params,
10057
							laf = segment.sweep > Math.PI ? 1 : 0,
10058
							sf = segment.anticlockwise ? 0 : 1;			
10059
10060
						return "M" + segment.x1 + " " + segment.y1 + " A " + segment.radius + " " + d.r + " 0 " + laf + "," + sf + " " + segment.x2 + " " + segment.y2;
10061
					}
10062
				})[segment.type]();	
10063
			}
10064
		}
10065
	};
10066
	
10067
// ******************************* /svg segments *****************************************************
10068
   
10069
    /*
10070
	 * Base class for SVG endpoints.
10071
	 */
10072
	var SvgEndpoint = window.SvgEndpoint = function(params) {
10073
		var _super = SvgComponent.apply(this, [ {
10074
				cssClass:params._jsPlumb.endpointClass, 
10075
				originalArgs:arguments, 
10076
				pointerEventsSpec:"all",
10077
				useDivWrapper:true,
10078
				_jsPlumb:params._jsPlumb
10079
			} ]);
10080
			
10081
		_super.renderer.paint = function(style) {
10082
			var s = jsPlumb.extend({}, style);
10083
			if (s.outlineColor) {
10084
				s.strokeWidth = s.outlineWidth;
10085
				s.strokeStyle = jsPlumbUtil.convertStyle(s.outlineColor, true);
10086
			}
10087
			
10088
			if (this.node == null) {
10089
				this.node = this.makeNode(s);
10090
				this.svg.appendChild(this.node);
10091
				this.attachListeners(this.node, this);
10092
			}
10093
			else if (this.updateNode != null) {
10094
				this.updateNode(this.node);
10095
			}
10096
			_applyStyles(this.svg, this.node, s, [ this.x, this.y, this.w, this.h ], this);
10097
			_pos(this.node, [ this.x, this.y ]);
10098
		}.bind(this);
10099
				
10100
	};
10101
	jsPlumbUtil.extend(SvgEndpoint, SvgComponent, {
10102
		reattachListeners : function() {
10103
			if (this.node) this.reattachListenersForElement(this.node, this);
10104
		}
10105
	});
10106
	
10107
	/*
10108
	 * SVG Dot Endpoint
10109
	 */
10110
	jsPlumb.Endpoints.svg.Dot = function() {
10111
		jsPlumb.Endpoints.Dot.apply(this, arguments);
10112
		SvgEndpoint.apply(this, arguments);		
10113
		this.makeNode = function(style) { 
10114
			return _node("circle", {
10115
                "cx"	:	this.w / 2,
10116
                "cy"	:	this.h / 2,
10117
                "r"		:	this.radius
10118
            });			
10119
		};
10120
		this.updateNode = function(node) {
10121
			_attr(node, {
10122
				"cx":this.w / 2,
10123
				"cy":this.h  / 2,
10124
				"r":this.radius
10125
			});
10126
		};
10127
	};
10128
	jsPlumbUtil.extend(jsPlumb.Endpoints.svg.Dot, [jsPlumb.Endpoints.Dot, SvgEndpoint]);
10129
	
10130
	/*
10131
	 * SVG Rectangle Endpoint 
10132
	 */
10133
	jsPlumb.Endpoints.svg.Rectangle = function() {
10134
		jsPlumb.Endpoints.Rectangle.apply(this, arguments);
10135
		SvgEndpoint.apply(this, arguments);		
10136
		this.makeNode = function(style) {
10137
			return _node("rect", {
10138
				"width"     :   this.w,
10139
				"height"    :   this.h
10140
			});
10141
		};
10142
		this.updateNode = function(node) {
10143
			_attr(node, {
10144
				"width":this.w,
10145
				"height":this.h
10146
			});
10147
		};			
10148
	};		
10149
	jsPlumbUtil.extend(jsPlumb.Endpoints.svg.Rectangle, [jsPlumb.Endpoints.Rectangle, SvgEndpoint]);
10150
	
10151
	/*
10152
	 * SVG Image Endpoint is the default image endpoint.
10153
	 */
10154
	jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image;
10155
	/*
10156
	 * Blank endpoint in svg renderer is the default Blank endpoint.
10157
	 */
10158
	jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank;	
10159
	/*
10160
	 * Label overlay in svg renderer is the default Label overlay.
10161
	 */
10162
	jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label;
10163
	/*
10164
	 * Custom overlay in svg renderer is the default Custom overlay.
10165
	 */
10166
	jsPlumb.Overlays.svg.Custom = jsPlumb.Overlays.Custom;
10167
		
10168
	var AbstractSvgArrowOverlay = function(superclass, originalArgs) {
10169
    	superclass.apply(this, originalArgs);
10170
    	jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
10171
        this.isAppendedAtTopLevel = false;
10172
    	var self = this;
10173
    	this.path = null;
10174
    	this.paint = function(params, containerExtents) {
10175
    		// only draws on connections, not endpoints.
10176
    		if (params.component.svg && containerExtents) {
10177
	    		if (this.path == null) {
10178
	    			this.path = _node("path", {
10179
	    				"pointer-events":"all"	
10180
	    			});
10181
	    			params.component.svg.appendChild(this.path);
10182
	    			
10183
	    			this.attachListeners(this.path, params.component);
10184
	    			this.attachListeners(this.path, this);
10185
	    		}
10186
	    		var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "",
10187
	    			offset = [0,0];
10188
10189
	    		if (containerExtents.xmin < 0) offset[0] = -containerExtents.xmin;
10190
	    		if (containerExtents.ymin < 0) offset[1] = -containerExtents.ymin;
10191
	    		
10192
	    		_attr(this.path, { 
10193
	    			"d"			:	makePath(params.d),
10194
	    			"class" 	:	clazz,
10195
	    			stroke 		: 	params.strokeStyle ? params.strokeStyle : null,
10196
	    			fill 		: 	params.fillStyle ? params.fillStyle : null,
10197
	    			transform	: 	"translate(" + offset[0] + "," + offset[1] + ")"
10198
	    		});    		
10199
	    	}
10200
    	};
10201
    	var makePath = function(d) {
10202
    		return "M" + d.hxy.x + "," + d.hxy.y +
10203
    				" L" + d.tail[0].x + "," + d.tail[0].y + 
10204
    				" L" + d.cxy.x + "," + d.cxy.y + 
10205
    				" L" + d.tail[1].x + "," + d.tail[1].y + 
10206
    				" L" + d.hxy.x + "," + d.hxy.y;
10207
    	};
10208
    	this.reattachListeners = function() {
10209
			if (this.path) this.reattachListenersForElement(this.path, this);
10210
		};		
10211
    };
10212
    jsPlumbUtil.extend(AbstractSvgArrowOverlay, [jsPlumb.jsPlumbUIComponent, jsPlumb.Overlays.AbstractOverlay], {
10213
    	cleanup : function() {
10214
    		if (this.path != null) jsPlumb.CurrentLibrary.removeElement(this.path);
10215
    	},
10216
    	setVisible:function(v) {
10217
    		if(this.path != null) (this.path.style.display = (v ? "block" : "none"));
10218
    	}
10219
    });
10220
    
10221
    jsPlumb.Overlays.svg.Arrow = function() {
10222
    	AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);    	
10223
    };
10224
    jsPlumbUtil.extend(jsPlumb.Overlays.svg.Arrow, [ jsPlumb.Overlays.Arrow, AbstractSvgArrowOverlay ]);
10225
    
10226
    jsPlumb.Overlays.svg.PlainArrow = function() {
10227
    	AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);    	
10228
    };
10229
    jsPlumbUtil.extend(jsPlumb.Overlays.svg.PlainArrow, [ jsPlumb.Overlays.PlainArrow, AbstractSvgArrowOverlay ]);
10230
    
10231
    jsPlumb.Overlays.svg.Diamond = function() {
10232
    	AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);    	
10233
    };
10234
    jsPlumbUtil.extend(jsPlumb.Overlays.svg.Diamond, [ jsPlumb.Overlays.Diamond, AbstractSvgArrowOverlay ]);
10235
10236
    // a test
10237
    jsPlumb.Overlays.svg.GuideLines = function() {
10238
        var path = null, self = this, p1_1, p1_2;        
10239
        jsPlumb.Overlays.GuideLines.apply(this, arguments);
10240
        this.paint = function(params, containerExtents) {
10241
    		if (path == null) {
10242
    			path = _node("path");
10243
    			params.connector.svg.appendChild(path);
10244
    			self.attachListeners(path, params.connector);
10245
    			self.attachListeners(path, self);
10246
10247
                p1_1 = _node("path");
10248
    			params.connector.svg.appendChild(p1_1);
10249
    			self.attachListeners(p1_1, params.connector);
10250
    			self.attachListeners(p1_1, self);
10251
10252
                p1_2 = _node("path");
10253
    			params.connector.svg.appendChild(p1_2);
10254
    			self.attachListeners(p1_2, params.connector);
10255
    			self.attachListeners(p1_2, self);
10256
    		}
10257
10258
    		var offset =[0,0];
10259
    		if (containerExtents.xmin < 0) offset[0] = -containerExtents.xmin;
10260
    		if (containerExtents.ymin < 0) offset[1] = -containerExtents.ymin;
10261
10262
    		_attr(path, {
10263
    			"d"		:	makePath(params.head, params.tail),
10264
    			stroke 	: 	"red",
10265
    			fill 	: 	null,
10266
    			transform:"translate(" + offset[0] + "," + offset[1] + ")"
10267
    		});
10268
10269
            _attr(p1_1, {
10270
    			"d"		:	makePath(params.tailLine[0], params.tailLine[1]),
10271
    			stroke 	: 	"blue",
10272
    			fill 	: 	null,
10273
    			transform:"translate(" + offset[0] + "," + offset[1] + ")"
10274
    		});
10275
10276
            _attr(p1_2, {
10277
    			"d"		:	makePath(params.headLine[0], params.headLine[1]),
10278
    			stroke 	: 	"green",
10279
    			fill 	: 	null,
10280
    			transform:"translate(" + offset[0] + "," + offset[1] + ")"
10281
    		});
10282
    	};
10283
10284
        var makePath = function(d1, d2) {
10285
            return "M " + d1.x + "," + d1.y +
10286
                   " L" + d2.x + "," + d2.y;
10287
        };        
10288
    };
10289
    jsPlumbUtil.extend(jsPlumb.Overlays.svg.GuideLines, jsPlumb.Overlays.GuideLines);
10290
})();
10291
/*
10292
 * jsPlumb
10293
 * 
10294
 * Title:jsPlumb 1.5.5
10295
 * 
10296
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
10297
 * elements, or VML.  
10298
 * 
10299
 * This file contains the VML renderers.
10300
 *
10301
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
10302
 * 
10303
 * http://jsplumb.org
10304
 * http://github.com/sporritt/jsplumb
10305
 * http://code.google.com/p/jsplumb
10306
 * 
10307
 * Dual licensed under the MIT and GPL2 licenses.
10308
 */
10309
10310
;(function() {
10311
	
10312
	// http://ajaxian.com/archives/the-vml-changes-in-ie-8
10313
	// http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/
10314
	// http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
10315
	
10316
	var vmlAttributeMap = {
10317
		"stroke-linejoin":"joinstyle",
10318
		"joinstyle":"joinstyle",		
10319
		"endcap":"endcap",
10320
		"miterlimit":"miterlimit"
10321
	},
10322
	jsPlumbStylesheet = null;
10323
	
10324
	if (document.createStyleSheet && document.namespaces) {			
10325
		
10326
		var ruleClasses = [
10327
				".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect", 
10328
				"jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group"
10329
			],
10330
			rule = "behavior:url(#default#VML);position:absolute;";
10331
10332
		jsPlumbStylesheet = document.createStyleSheet();
10333
10334
		for (var i = 0; i < ruleClasses.length; i++)
10335
			jsPlumbStylesheet.addRule(ruleClasses[i], rule);
10336
10337
		// in this page it is also mentioned that IE requires the extra arg to the namespace
10338
		// http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
10339
		// but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either.
10340
		// var iev = document.documentMode;
10341
		//if (!iev || iev < 8)
10342
			document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml");
10343
		//else
10344
		//	document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML");
10345
	}
10346
	
10347
	jsPlumb.vml = {};
10348
	
10349
	var scale = 1000,
10350
10351
    _groupMap = {},
10352
    _getGroup = function(container, connectorClass) {
10353
        var id = jsPlumb.getId(container),
10354
            g = _groupMap[id];
10355
        if(!g) {
10356
            g = _node("group", [0,0,scale, scale], {"class":connectorClass});
10357
            //g.style.position=absolute;
10358
            //g["coordsize"] = "1000,1000";
10359
            g.style.backgroundColor="red";
10360
            _groupMap[id] = g;
10361
            //jsPlumb.appendElement(g, container);  // todo if this gets reinstated, remember to use the current jsplumb instance.
10362
            //jsPlumb.CurrentLibrary.getDOMElement(container).appendChild(g);
10363
            //document.body.appendChild(g);
10364
        }
10365
        return g;
10366
    },
10367
	_atts = function(o, atts) {
10368
		for (var i in atts) { 
10369
			// IE8 fix: setattribute does not work after an element has been added to the dom!
10370
			// http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
10371
			//o.setAttribute(i, atts[i]);
10372
10373
			/*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following:
10374
10375
			if (document.documentMode==8) {
10376
			ele.opacity=1;
10377
			} else {
10378
			ele.setAttribute(‘opacity’,1);
10379
			}
10380
			*/
10381
10382
			o[i] = atts[i];
10383
		}
10384
	},
10385
	_node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) {
10386
		atts = atts || {};
10387
		var o = document.createElement("jsplumb:" + name);
10388
		if (deferToJsPlumbContainer)
10389
			_jsPlumb.appendElement(o, parent);
10390
		else
10391
			jsPlumb.CurrentLibrary.appendElement(o, parent);
10392
		o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml";
10393
		_pos(o, d);
10394
		_atts(o, atts);
10395
		return o;
10396
	},
10397
	_pos = function(o,d, zIndex) {
10398
		o.style.left = d[0] + "px";		
10399
		o.style.top =  d[1] + "px";
10400
		o.style.width= d[2] + "px";
10401
		o.style.height= d[3] + "px";
10402
		o.style.position = "absolute";
10403
		if (zIndex)
10404
			o.style.zIndex = zIndex;
10405
	},
10406
	_conv = jsPlumb.vml.convertValue = function(v) {
10407
		return Math.floor(v * scale);
10408
	},	
10409
	// tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so,
10410
	// or 1 if not.  TODO in the future, support variable opacity.
10411
	_maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) {
10412
		if ("transparent" === styleToCheck)
10413
			component.setOpacity(type, "0.0");
10414
		else
10415
			component.setOpacity(type, "1.0");
10416
	},
10417
	_applyStyles = function(node, style, component, _jsPlumb) {
10418
		var styleToWrite = {};
10419
		if (style.strokeStyle) {
10420
			styleToWrite.stroked = "true";
10421
			var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true);
10422
			styleToWrite.strokecolor = strokeColor;
10423
			_maybeSetOpacity(styleToWrite, strokeColor, "stroke", component);
10424
			styleToWrite.strokeweight = style.lineWidth + "px";
10425
		}
10426
		else styleToWrite.stroked = "false";
10427
		
10428
		if (style.fillStyle) {
10429
			styleToWrite.filled = "true";
10430
			var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true);
10431
			styleToWrite.fillcolor = fillColor;
10432
			_maybeSetOpacity(styleToWrite, fillColor, "fill", component);
10433
		}
10434
		else styleToWrite.filled = "false";
10435
		
10436
		if(style.dashstyle) {
10437
			if (component.strokeNode == null) {
10438
				component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style.dashstyle }, node, _jsPlumb);				
10439
			}
10440
			else
10441
				component.strokeNode.dashstyle = style.dashstyle;
10442
		}					
10443
		else if (style["stroke-dasharray"] && style.lineWidth) {
10444
			var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",",
10445
			parts = style["stroke-dasharray"].split(sep),
10446
			styleToUse = "";
10447
			for(var i = 0; i < parts.length; i++) {
10448
				styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep);
10449
			}
10450
			if (component.strokeNode == null) {
10451
				component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb);				
10452
			}
10453
			else
10454
				component.strokeNode.dashstyle = styleToUse;
10455
		}
10456
		
10457
		_atts(node, styleToWrite);
10458
	},
10459
	/*
10460
	 * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent. 
10461
	 */
10462
	VmlComponent = function() {				
10463
		var self = this, renderer = {};
10464
		jsPlumb.jsPlumbUIComponent.apply(this, arguments);	
10465
10466
		this.opacityNodes = {
10467
			"stroke":null,
10468
			"fill":null
10469
		};
10470
		this.initOpacityNodes = function(vml) {
10471
			self.opacityNodes.stroke = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb.instance);
10472
			self.opacityNodes.fill = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb.instance);							
10473
		};
10474
		this.setOpacity = function(type, value) {
10475
			var node = self.opacityNodes[type];
10476
			if (node) node.opacity = "" + value;
10477
		};
10478
		var displayElements = [ ];
10479
		this.getDisplayElements = function() { 
10480
			return displayElements; 
10481
		};
10482
		
10483
		this.appendDisplayElement = function(el, doNotAppendToCanvas) {
10484
			if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el);
10485
			displayElements.push(el);
10486
		};
10487
	};
10488
	jsPlumbUtil.extend(VmlComponent, jsPlumb.jsPlumbUIComponent, {
10489
		cleanup:function() {			
10490
			if (this.bgCanvas) jsPlumbUtil.removeElement(this.bgCanvas);
10491
			jsPlumbUtil.removeElement(this.canvas);            				
10492
		}
10493
	});
10494
10495
	/*
10496
	 * Base class for Vml connectors. extends VmlComponent.
10497
	 */
10498
	var VmlConnector = jsPlumb.ConnectorRenderers.vml = function(params) {		
10499
		this.strokeNode = null;
10500
		this.canvas = null;
10501
		VmlComponent.apply(this, arguments);
10502
		var clazz = this._jsPlumb.instance.connectorClass + (params.cssClass ? (" " + params.cssClass) : "");
10503
		this.paint = function(style) {		
10504
			if (style !== null) {			
10505
10506
				// we need to be at least 1 pixel in each direction, because otherwise coordsize gets set to
10507
				// 0 and overlays cannot paint.
10508
				this.w = Math.max(this.w, 1);
10509
				this.h = Math.max(this.h, 1);
10510
10511
				var segments = this.getSegments(), p = { "path":"" },
10512
                    d = [this.x, this.y, this.w, this.h];
10513
				
10514
				// create path from segments.	
10515
				for (var i = 0; i < segments.length; i++) {
10516
					p.path += jsPlumb.Segments.vml.SegmentRenderer.getPath(segments[i]);
10517
					p.path += " ";
10518
				}
10519
10520
                //*
10521
				if (style.outlineColor) {
10522
					var outlineWidth = style.outlineWidth || 1,
10523
					outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
10524
					outlineStyle = {
10525
						strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor),
10526
						lineWidth : outlineStrokeWidth
10527
					};
10528
					for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa];
10529
					
10530
					if (this.bgCanvas == null) {						
10531
						p["class"] = clazz;
10532
						p.coordsize = (d[2] * scale) + "," + (d[3] * scale);
10533
						this.bgCanvas = _node("shape", d, p, params.parent, this._jsPlumb.instance, true);						
10534
						_pos(this.bgCanvas, d);
10535
						this.appendDisplayElement(this.bgCanvas, true);	
10536
						this.attachListeners(this.bgCanvas, this);					
10537
						this.initOpacityNodes(this.bgCanvas, ["stroke"]);		
10538
					}
10539
					else {
10540
						p.coordsize = (d[2] * scale) + "," + (d[3] * scale);
10541
						_pos(this.bgCanvas, d);
10542
						_atts(this.bgCanvas, p);
10543
					}
10544
					
10545
					_applyStyles(this.bgCanvas, outlineStyle, this);
10546
				}
10547
				//*/
10548
				
10549
				if (this.canvas == null) {										
10550
					p["class"] = clazz;
10551
					p.coordsize = (d[2] * scale) + "," + (d[3] * scale);					
10552
					this.canvas = _node("shape", d, p, params.parent, this._jsPlumb.instance, true);					                
10553
                    //var group = _getGroup(params.parent);                   // test of append everything to a group
10554
                    //group.appendChild(self.canvas);                           // sort of works but not exactly;
10555
					//params["_jsPlumb"].appendElement(self.canvas, params.parent);    //before introduction of groups
10556
10557
					this.appendDisplayElement(this.canvas, true);										
10558
					this.attachListeners(this.canvas, this);					
10559
					this.initOpacityNodes(this.canvas, ["stroke"]);		
10560
				}
10561
				else {
10562
					p.coordsize = (d[2] * scale) + "," + (d[3] * scale);
10563
					_pos(this.canvas, d);
10564
					_atts(this.canvas, p);
10565
				}
10566
				
10567
				_applyStyles(this.canvas, style, this, this._jsPlumb.instance);
10568
			}
10569
		};	
10570
				
10571
	};
10572
	jsPlumbUtil.extend(VmlConnector, VmlComponent, {
10573
		reattachListeners : function() {
10574
			if (this.canvas) this.reattachListenersForElement(this.canvas, this);
10575
		},
10576
		setVisible:function(v) {
10577
			if (this.canvas) {
10578
				this.canvas.style.display = v ? "block" : "none";
10579
			}
10580
			if (this.bgCanvas) {
10581
				this.bgCanvas.style.display = v ? "block" : "none";
10582
			}
10583
		}
10584
	});	
10585
	
10586
	/*
10587
	 * 
10588
	 * Base class for Vml Endpoints. extends VmlComponent.
10589
	 * 
10590
	 */
10591
	var VmlEndpoint = window.VmlEndpoint = function(params) {
10592
		VmlComponent.apply(this, arguments);
10593
		this._jsPlumb.vml = null;//, opacityStrokeNode = null, opacityFillNode = null;
10594
		this.canvas = document.createElement("div");
10595
		this.canvas.style.position = "absolute";
10596
		this._jsPlumb.clazz = this._jsPlumb.instance.endpointClass + (params.cssClass ? (" " + params.cssClass) : "");
10597
10598
		// TODO vml endpoint adds class to VML at constructor time.  but the addClass method adds VML
10599
		// to the enclosing DIV. what to do?  seems like it would be better to just target the div.
10600
		// HOWEVER...vml connection has no containing div.  why not? it feels like it should.
10601
10602
		//var group = _getGroup(params.parent);
10603
        //group.appendChild(self.canvas);
10604
		params._jsPlumb.appendElement(this.canvas, params.parent);
10605
10606
		this.paint = function(style, anchor) {
10607
			var p = { }, vml = this._jsPlumb.vml;				
10608
			
10609
			jsPlumbUtil.sizeElement(this.canvas, this.x, this.y, this.w, this.h);
10610
			if (this._jsPlumb.vml == null) {
10611
				p["class"] = this._jsPlumb.clazz;
10612
				vml = this._jsPlumb.vml = this.getVml([0,0, this.w, this.h], p, anchor, this.canvas, this._jsPlumb.instance);				
10613
				this.attachListeners(vml, this);
10614
10615
				this.appendDisplayElement(vml, true);
10616
				this.appendDisplayElement(this.canvas, true);
10617
				
10618
				this.initOpacityNodes(vml, ["fill"]);			
10619
			}
10620
			else {				
10621
				_pos(vml, [0,0, this.w, this.h]);
10622
				_atts(vml, p);
10623
			}
10624
			
10625
			_applyStyles(vml, style, this);
10626
		};		
10627
	};
10628
	jsPlumbUtil.extend(VmlEndpoint, VmlComponent, {
10629
		reattachListeners : function() {
10630
			if (this._jsPlumb.vml) this.reattachListenersForElement(this._jsPlumb.vml, this);
10631
		}
10632
	});
10633
	
10634
// ******************************* vml segments *****************************************************	
10635
		
10636
	jsPlumb.Segments.vml = {
10637
		SegmentRenderer : {		
10638
			getPath : function(segment) {
10639
				return ({
10640
					"Straight":function(segment) {
10641
						var d = segment.params;
10642
						return "m" + _conv(d.x1) + "," + _conv(d.y1) + " l" + _conv(d.x2) + "," + _conv(d.y2) + " e";
10643
					},
10644
					"Bezier":function(segment) {
10645
						var d = segment.params;
10646
						return "m" + _conv(d.x1) + "," + _conv(d.y1) + 
10647
				   			" c" + _conv(d.cp1x) + "," + _conv(d.cp1y) + "," + _conv(d.cp2x) + "," + _conv(d.cp2y) + "," + _conv(d.x2) + "," + _conv(d.y2) + " e";
10648
					},
10649
					"Arc":function(segment) {					
10650
						var d = segment.params,
10651
							xmin = Math.min(d.x1, d.x2),
10652
							xmax = Math.max(d.x1, d.x2),
10653
							ymin = Math.min(d.y1, d.y2),
10654
							ymax = Math.max(d.y1, d.y2),														
10655
							sf = segment.anticlockwise ? 1 : 0,
10656
							pathType = (segment.anticlockwise ? "at " : "wa "),
10657
							makePosString = function() {
10658
								if (d.loopback)
10659
									return "0,0," + _conv(2*d.r) + "," + _conv(2 * d.r);
10660
10661
								var xy = [
10662
										null,
10663
										[ function() { return [xmin, ymin ];}, function() { return [xmin - d.r, ymin - d.r ];}],
10664
										[ function() { return [xmin - d.r, ymin ];}, function() { return [xmin, ymin - d.r ];}],
10665
										[ function() { return [xmin - d.r, ymin - d.r ];}, function() { return [xmin, ymin ];}],
10666
										[ function() { return [xmin, ymin - d.r ];}, function() { return [xmin - d.r, ymin ];}]
10667
									][segment.segment][sf]();
10668
10669
								return _conv(xy[0]) + "," + _conv(xy[1]) + "," + _conv(xy[0] + (2*d.r)) + "," + _conv(xy[1] + (2*d.r));
10670
							};
10671
10672
						return pathType + " " + makePosString() + "," + _conv(d.x1) + "," + _conv(d.y1) + "," + _conv(d.x2) + "," + _conv(d.y2) + " e";												
10673
					}
10674
						
10675
				})[segment.type](segment);	
10676
			}
10677
		}
10678
	};
10679
	
10680
// ******************************* /vml segments *****************************************************	
10681
10682
// ******************************* vml endpoints *****************************************************
10683
	
10684
	jsPlumb.Endpoints.vml.Dot = function() {
10685
		jsPlumb.Endpoints.Dot.apply(this, arguments);
10686
		VmlEndpoint.apply(this, arguments);
10687
		this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); };
10688
	};
10689
	jsPlumbUtil.extend(jsPlumb.Endpoints.vml.Dot, VmlEndpoint);
10690
	
10691
	jsPlumb.Endpoints.vml.Rectangle = function() {
10692
		jsPlumb.Endpoints.Rectangle.apply(this, arguments);
10693
		VmlEndpoint.apply(this, arguments);
10694
		this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); };
10695
	};
10696
	jsPlumbUtil.extend(jsPlumb.Endpoints.vml.Rectangle, VmlEndpoint);
10697
	
10698
	/*
10699
	 * VML Image Endpoint is the same as the default image endpoint.
10700
	 */
10701
	jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image;
10702
	
10703
	/**
10704
	 * placeholder for Blank endpoint in vml renderer.
10705
	 */
10706
	jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank;
10707
	
10708
// ******************************* /vml endpoints *****************************************************	
10709
10710
// ******************************* vml overlays *****************************************************
10711
	
10712
	/**
10713
	 * VML Label renderer. uses the default label renderer (which adds an element to the DOM)
10714
	 */
10715
	jsPlumb.Overlays.vml.Label  = jsPlumb.Overlays.Label;
10716
	
10717
	/**
10718
	 * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM)
10719
	 */
10720
	jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom;
10721
	
10722
	/**
10723
	 * Abstract VML arrow superclass
10724
	 */
10725
	var AbstractVmlArrowOverlay = function(superclass, originalArgs) {
10726
    	superclass.apply(this, originalArgs);
10727
    	VmlComponent.apply(this, originalArgs);
10728
    	var self = this, path = null;
10729
    	self.canvas = null; 
10730
    	self.isAppendedAtTopLevel = true;
10731
    	var getPath = function(d) {    		
10732
    		return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) +
10733
    		       " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) + 
10734
    		       " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) + 
10735
    		       " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) + 
10736
    		       " x e";
10737
    	};
10738
    	this.paint = function(params, containerExtents) {
10739
    		// only draws for connectors, not endpoints.
10740
    		if (params.component.canvas && containerExtents) {
10741
	    		var p = {}, d = params.d, connector = params.component;
10742
				if (params.strokeStyle) {
10743
					p.stroked = "true";
10744
					p.strokecolor = jsPlumbUtil.convertStyle(params.strokeStyle, true);    				
10745
				}
10746
				if (params.lineWidth) p.strokeweight = params.lineWidth + "px";
10747
				if (params.fillStyle) {
10748
					p.filled = "true";
10749
					p.fillcolor = params.fillStyle;
10750
				}			
10751
10752
				var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
10753
					ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
10754
					xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
10755
					ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
10756
					w = Math.abs(xmax - xmin),
10757
					h = Math.abs(ymax - ymin),
10758
					dim = [xmin, ymin, w, h];
10759
10760
				// for VML, we create overlays using shapes that have the same dimensions and
10761
				// coordsize as their connector - overlays calculate themselves relative to the
10762
				// connector (it's how it's been done since the original canvas implementation, because
10763
				// for canvas that makes sense).
10764
				p.path = getPath(d);
10765
				p.coordsize = (connector.w * scale) + "," + (connector.h * scale);			
10766
				
10767
				dim[0] = connector.x;
10768
				dim[1] = connector.y;
10769
				dim[2] = connector.w;
10770
				dim[3] = connector.h;
10771
				
10772
	    		if (self.canvas == null) {
10773
	    			var overlayClass = connector._jsPlumb.overlayClass || "";
10774
	    			var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "";
10775
	    			p["class"] = clazz + " " + overlayClass;
10776
					self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb.instance, true);								
10777
					connector.appendDisplayElement(self.canvas, true);
10778
					self.attachListeners(self.canvas, connector);
10779
					self.attachListeners(self.canvas, self);
10780
				}
10781
				else {				
10782
					_pos(self.canvas, dim);
10783
					_atts(self.canvas, p);
10784
				}    		
10785
			}
10786
    	};
10787
    	
10788
    	this.reattachListeners = function() {
10789
			if (self.canvas) self.reattachListenersForElement(self.canvas, self);
10790
		};
10791
10792
		this.cleanup = function() {
10793
    		if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas);
10794
    	};
10795
    };
10796
    jsPlumbUtil.extend(AbstractVmlArrowOverlay, [VmlComponent, jsPlumb.Overlays.AbstractOverlay], {
10797
    	setVisible : function(state) {
10798
    	    this.canvas.style.display = state ? "block" : "none";
10799
    	}
10800
    });
10801
	
10802
	jsPlumb.Overlays.vml.Arrow = function() {
10803
    	AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);    	
10804
    };
10805
    jsPlumbUtil.extend(jsPlumb.Overlays.vml.Arrow, [ jsPlumb.Overlays.Arrow, AbstractVmlArrowOverlay ]);
10806
    
10807
    jsPlumb.Overlays.vml.PlainArrow = function() {
10808
    	AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);    	
10809
    };
10810
    jsPlumbUtil.extend(jsPlumb.Overlays.vml.PlainArrow, [ jsPlumb.Overlays.PlainArrow, AbstractVmlArrowOverlay ]);
10811
    
10812
    jsPlumb.Overlays.vml.Diamond = function() {
10813
    	AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);    	
10814
    };
10815
    jsPlumbUtil.extend(jsPlumb.Overlays.vml.Diamond, [ jsPlumb.Overlays.Diamond, AbstractVmlArrowOverlay ]);
10816
    
10817
// ******************************* /vml overlays *****************************************************    
10818
    
10819
})();
10820
/*
10821
 * jsPlumb
10822
 * 
10823
 * Title:jsPlumb 1.5.5
10824
 * 
10825
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
10826
 * elements, or VML.  
10827
 * 
10828
 * This file contains the jQuery adapter.
10829
 *
10830
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
10831
 * 
10832
 * http://jsplumb.org
10833
 * http://github.com/sporritt/jsplumb
10834
 * http://code.google.com/p/jsplumb
10835
 * 
10836
 * Dual licensed under the MIT and GPL2 licenses.
10837
 */ 
10838
/* 
10839
 * the library specific functions, such as find offset, get id, get attribute, extend etc.  
10840
 * the full list is:
10841
 * 
10842
 * addClass				adds a class to the given element
10843
 * animate				calls the underlying library's animate functionality
10844
 * appendElement		appends a child element to a parent element.
10845
 * bind					binds some event to an element
10846
 * dragEvents			a dictionary of event names
10847
 * extend				extend some js object with another.  probably not overly necessary; jsPlumb could just do this internally.
10848
 * getDragObject		gets the object that is being dragged, by extracting it from the arguments passed to a drag callback
10849
 * getDragScope			gets the drag scope for a given element.
10850
 * getDropScope			gets the drop scope for a given element.
10851
 * getElementObject		turns an id or dom element into an element object of the underlying library's type.
10852
 * getOffset			gets an element's offset
10853
 * getOriginalEvent     gets the original browser event from some wrapper event
10854
 * getPageXY			gets the page event's xy location.
10855
 * getParent			gets the parent of some element.
10856
 * getScrollLeft		gets an element's scroll left.  TODO: is this actually used?  will it be?
10857
 * getScrollTop			gets an element's scroll top.  TODO: is this actually used?  will it be?
10858
 * getSize				gets an element's size.
10859
 * getUIPosition		gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback.
10860
 * hasClass				returns whether or not the given element has the given class.
10861
 * initDraggable		initializes an element to be draggable 
10862
 * initDroppable		initializes an element to be droppable
10863
 * isDragSupported		returns whether or not drag is supported for some element.
10864
 * isDropSupported		returns whether or not drop is supported for some element.
10865
 * removeClass			removes a class from a given element.
10866
 * removeElement		removes some element completely from the DOM. 
10867
 * setDragFilter		sets a filter for some element that indicates areas of the element that should not respond to dragging.
10868
 * setDraggable			sets whether or not some element should be draggable.
10869
 * setDragScope			sets the drag scope for a given element.
10870
 * setOffset			sets the offset of some element.
10871
 * trigger				triggers some event on an element.
10872
 * unbind				unbinds some listener from some element.
10873
 */
10874
(function($) {	
10875
	
10876
	//var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement;
10877
10878
	var _getElementObject = function(el) {			
10879
		return typeof(el) == "string" ? $("#" + el) : $(el);
10880
	};
10881
10882
	jsPlumb.CurrentLibrary = {					        
10883
		
10884
		/**
10885
		 * adds the given class to the element object.
10886
		 */
10887
		addClass : function(el, clazz) {
10888
			el = _getElementObject(el);
10889
			try {
10890
				if (el[0].className.constructor == SVGAnimatedString) {
10891
					jsPlumbUtil.svg.addClass(el[0], clazz);                    
10892
				}
10893
			}
10894
			catch (e) {
10895
				// SVGAnimatedString not supported; no problem.
10896
			}
10897
            try {                
10898
                el.addClass(clazz);
10899
            }
10900
            catch (e) {
10901
                // you probably have jQuery 1.9 and Firefox.  
10902
            }
10903
		},
10904
		
10905
		/**
10906
		 * animates the given element.
10907
		 */
10908
		animate : function(el, properties, options) {
10909
			el.animate(properties, options);
10910
		},				
10911
		
10912
		/**
10913
		 * appends the given child to the given parent.
10914
10915
TODO: REMOVE!
10916
10917
		 */
10918
		appendElement : function(child, parent) {
10919
			_getElementObject(parent).append(child);			
10920
		},   
10921
10922
		/**
10923
		* executes an ajax call.
10924
		*/
10925
		ajax : function(params) {
10926
			params = params || {};
10927
			params.type = params.type || "get";
10928
			$.ajax(params);
10929
		},
10930
		
10931
		/**
10932
		 * event binding wrapper.  it just so happens that jQuery uses 'bind' also.  yui3, for example,
10933
		 * uses 'on'.
10934
		 */
10935
		bind : function(el, event, callback) {
10936
			el = _getElementObject(el);
10937
			el.bind(event, callback);
10938
		},
10939
10940
		destroyDraggable : function(el) {
10941
			if ($(el).data("draggable"))
10942
				$(el).draggable("destroy");
10943
		},
10944
10945
		destroyDroppable : function(el) {
10946
			if ($(el).data("droppable"))
10947
				$(el).droppable("destroy");
10948
		},
10949
		
10950
		/**
10951
         * mapping of drag events for jQuery
10952
         */
10953
		dragEvents : {
10954
			'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step',
10955
			'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete'
10956
		},
10957
				
10958
		/**
10959
		 * wrapper around the library's 'extend' functionality (which it hopefully has.
10960
		 * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
10961
		 * instead.  it's not like its hard.
10962
		 */
10963
		extend : function(o1, o2) {
10964
			return $.extend(o1, o2);
10965
		},		
10966
		
10967
		getClientXY : function(eventObject) {
10968
			return [eventObject.clientX, eventObject.clientY];
10969
		},
10970
		
10971
		/**
10972
		 * takes the args passed to an event function and returns you an object representing that which is being dragged.
10973
		 */
10974
		getDragObject : function(eventArgs) {
10975
			return eventArgs[1].draggable || eventArgs[1].helper;
10976
		},
10977
		
10978
		getDragScope : function(el) {
10979
			return $(el).draggable("option", "scope");
10980
		},
10981
10982
		getDropEvent : function(args) {
10983
			return args[0];
10984
		},
10985
		
10986
		getDropScope : function(el) {
10987
			return $(el).droppable("option", "scope");		
10988
		},
10989
10990
		/**
10991
		* gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById),
10992
		* a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other
10993
		* two cases).  this is the opposite of getElementObject below.
10994
		*/
10995
		getDOMElement : function(el) {
10996
			if (el == null) return null;
10997
			if (typeof(el) == "string") return document.getElementById(el);
10998
			else if (el.context || el.length != null) return el[0];
10999
			else return el;
11000
		},
11001
	
11002
		/**
11003
		 * gets an "element object" from the given input.  this means an object that is used by the
11004
		 * underlying library on which jsPlumb is running.  'el' may already be one of these objects,
11005
		 * in which case it is returned as-is.  otherwise, 'el' is a String, the library's lookup 
11006
		 * function is used to find the element, using the given String as the element's id.
11007
		 * 
11008
		 */		
11009
		getElementObject : _getElementObject,
11010
		
11011
		/**
11012
		  * gets the offset for the element object.  this should return a js object like this:
11013
		  *
11014
		  * { left:xxx, top: xxx }
11015
		 */
11016
		getOffset : function(el) {
11017
			return el.offset();
11018
		},
11019
11020
		getOriginalEvent : function(e) {
11021
			return e.originalEvent;
11022
		},
11023
		
11024
		getPageXY : function(eventObject) {
11025
			return [eventObject.pageX, eventObject.pageY];
11026
		},
11027
		
11028
		getParent : function(el) {
11029
			return _getElementObject(el).parent();
11030
		},
11031
														
11032
		getScrollLeft : function(el) {
11033
			return el.scrollLeft();
11034
		},
11035
		
11036
		getScrollTop : function(el) {
11037
			return el.scrollTop();
11038
		},
11039
		
11040
		getSelector : function(context, spec) {
11041
            if (arguments.length == 2)
11042
                return _getElementObject(context).find(spec);
11043
            else
11044
                return $(context);
11045
		},
11046
		
11047
		/**
11048
		 * gets the size for the element object, in an array : [ width, height ].
11049
		 */
11050
		getSize : function(el) {
11051
			el = $(el);
11052
			return [el.outerWidth(), el.outerHeight()];
11053
		},
11054
11055
        getTagName : function(el) {
11056
            var e = _getElementObject(el);
11057
            return e.length > 0 ? e[0].tagName : null;
11058
        },
11059
		
11060
		/**
11061
		 * takes the args passed to an event function and returns you an object that gives the
11062
		 * position of the object being moved, as a js object with the same params as the result of
11063
		 * getOffset, ie: { left: xxx, top: xxx }.
11064
		 * 
11065
		 * different libraries have different signatures for their event callbacks.  
11066
		 * see getDragObject as well
11067
		 */
11068
		getUIPosition : function(eventArgs, zoom) {
11069
			
11070
			zoom = zoom || 1;
11071
			// this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes
11072
			// in the wrong offset if the element has a margin (it doesn't take the margin into account).  the getBoundingClientRect
11073
			// method, which is in pretty much all browsers now, reports the right numbers.  but it introduces a noticeable lag, which
11074
			// i don't like.
11075
            
11076
			/*if ( getBoundingClientRectSupported ) {
11077
				var r = eventArgs[1].helper[0].getBoundingClientRect();
11078
				return { left : r.left, top: r.top };
11079
			} else {*/
11080
			if (eventArgs.length == 1) {
11081
				ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY };
11082
			}
11083
			else {
11084
				var ui = eventArgs[1],
11085
				  _offset = ui.offset;
11086
				  
11087
				ret = _offset || ui.absolutePosition;
11088
				
11089
				// adjust ui position to account for zoom, because jquery ui does not do this.
11090
				ui.position.left /= zoom;
11091
				ui.position.top /= zoom;
11092
			}
11093
            return { left:ret.left / zoom, top: ret.top / zoom };
11094
		},		
11095
		
11096
		hasClass : function(el, clazz) {
11097
			return el.hasClass(clazz);
11098
		},
11099
		
11100
		/**
11101
		 * initialises the given element to be draggable.
11102
		 */
11103
		initDraggable : function(el, options, isPlumbedComponent, _jsPlumb) {
11104
			options = options || {};
11105
			el = $(el);
11106
11107
			options.start = jsPlumbUtil.wrap(options.start, function() {
11108
				$("body").addClass(_jsPlumb.dragSelectClass);
11109
			}, false);
11110
11111
			options.stop = jsPlumbUtil.wrap(options.stop, function() {
11112
				$("body").removeClass(_jsPlumb.dragSelectClass);
11113
			});
11114
11115
			// remove helper directive if present and no override
11116
			if (!options.doNotRemoveHelper)
11117
				options.helper = null;
11118
			if (isPlumbedComponent)
11119
				options.scope = options.scope || jsPlumb.Defaults.Scope;
11120
			el.draggable(options);
11121
		},
11122
		
11123
		/**
11124
		 * initialises the given element to be droppable.
11125
		 */
11126
		initDroppable : function(el, options) {
11127
			options.scope = options.scope || jsPlumb.Defaults.Scope;
11128
			$(el).droppable(options);
11129
		},
11130
		
11131
		isAlreadyDraggable : function(el) {
11132
			return $(el).hasClass("ui-draggable");
11133
		},
11134
		
11135
		/**
11136
		 * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element.
11137
		 */
11138
		isDragSupported : function(el, options) {
11139
			return $(el).draggable;
11140
		},				
11141
						
11142
		/**
11143
		 * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element.
11144
		 */
11145
		isDropSupported : function(el, options) {
11146
			return $(el).droppable;
11147
		},							
11148
		
11149
		/**
11150
		 * removes the given class from the element object.
11151
		 */
11152
		removeClass : function(el, clazz) {
11153
			el = _getElementObject(el);
11154
			try {
11155
				if (el[0].className.constructor == SVGAnimatedString) {
11156
					jsPlumbUtil.svg.removeClass(el[0], clazz);
11157
                    return;
11158
				}
11159
			}
11160
			catch (e) {
11161
				// SVGAnimatedString not supported; no problem.
11162
			}
11163
			el.removeClass(clazz);
11164
		},
11165
		
11166
		removeElement : function(element) {			
11167
			_getElementObject(element).remove();
11168
		},		
11169
11170
		setDragFilter : function(el, filter) {
11171
			if (jsPlumb.CurrentLibrary.isAlreadyDraggable(el))
11172
				el.draggable("option", "cancel", filter);
11173
		},
11174
		
11175
		setDraggable : function(el, draggable) {
11176
			el.draggable("option", "disabled", !draggable);
11177
		},
11178
		
11179
		setDragScope : function(el, scope) {
11180
			el.draggable("option", "scope", scope);
11181
		},
11182
		
11183
		setOffset : function(el, o) {
11184
			_getElementObject(el).offset(o);
11185
		},
11186
		
11187
		/**
11188
		 * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
11189
		 * the other libraries do not.  yui, in fact, cannot even pass an original event.  we have to pull out stuff
11190
		 * from the originalEvent to put in an options object for YUI. 
11191
		 * @param el
11192
		 * @param event
11193
		 * @param originalEvent
11194
		 */
11195
		trigger : function(el, event, originalEvent) {
11196
			var h = jQuery._data(_getElementObject(el)[0], "handle");
11197
            h(originalEvent);
11198
		},
11199
		
11200
		unbind : function(el, event, callback) {
11201
			el = _getElementObject(el);
11202
			el.unbind(event, callback);
11203
		}
11204
	};
11205
	
11206
	$(document).ready(jsPlumb.init);
11207
	
11208
})(jQuery);
11209
11210